From cbe4464178eac58ee5a847e5fb952b9ecb3055b1 Mon Sep 17 00:00:00 2001 From: epoxa Date: Sat, 19 Aug 2017 12:13:21 +0700 Subject: [PATCH 01/72] Fix oauth controller wrong session method call (#1226) --- src/Forum/Controller/AbstractOAuth2Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Forum/Controller/AbstractOAuth2Controller.php b/src/Forum/Controller/AbstractOAuth2Controller.php index 5083e793d..52f8c1e17 100644 --- a/src/Forum/Controller/AbstractOAuth2Controller.php +++ b/src/Forum/Controller/AbstractOAuth2Controller.php @@ -66,7 +66,7 @@ abstract class AbstractOAuth2Controller implements ControllerInterface return new RedirectResponse($authUrl.'&display=popup'); } elseif (! $state || $state !== $session->get('oauth2state')) { - $session->forget('oauth2state'); + $session->remove('oauth2state'); echo 'Invalid state. Please close the window and try again.'; exit; } From ad153c84844fc5ef2ff8dd1c9089e0de113af338 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 12 Sep 2017 20:41:17 +0200 Subject: [PATCH 02/72] Issue template: Explanation first --- .github/ISSUE_TEMPLATE.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 4f940e093..e7a082240 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,13 @@ --- > Try to complete the below form as far as you are able and are willing to share. Add a screenshot of the issue if you can. -## Bug report +## Explanation + +Explain, in simple terms, but with as much detail as possible, your issue. +Be specific: What happened? What would you expect to happen? What have you tried so far? + +## Technical details + - Version of Flarum: x.y.z - Website URL where the bug is visible: http://example.com - The webserver you are running: apache, nginx or something else @@ -16,9 +22,6 @@ Output of "php flarum info", run this in terminal in your Flarum directory. ``` -## Additional comments -Some additional information you'd like to share, eg what have you tried so far. - ## Log files ``` From 1e8399c0149e26cafd229a28536b13e2d22b358b Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Fri, 15 Sep 2017 13:27:11 +0200 Subject: [PATCH 03/72] Update zend-diactoros to v1.6 This release contains a useful fix for Content-Length problems that we have experienced before. See https://github.com/zendframework/zend-diactoros/releases/tag/1.6.0. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index efdb49182..1ee4195f0 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/yaml": "^2.7", "s9e/text-formatter": "^0.8.1", "tobscure/json-api": "^0.3.0", - "zendframework/zend-diactoros": "^1.1", + "zendframework/zend-diactoros": "^1.6", "zendframework/zend-stratigility": "^1.3" }, "require-dev": { From 084f74946d74ddde14f42bbb1b0ef75ab9ef6a09 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 12:13:24 +0930 Subject: [PATCH 04/72] Allow setting the raw content of a CommentPost --- src/Core/Post/CommentPost.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Core/Post/CommentPost.php b/src/Core/Post/CommentPost.php index 492088552..3db0c7555 100755 --- a/src/Core/Post/CommentPost.php +++ b/src/Core/Post/CommentPost.php @@ -153,6 +153,16 @@ class CommentPost extends Post $this->attributes['content'] = $value ? static::$formatter->parse($value, $this) : null; } + /** + * Set the parsed/raw content. + * + * @param string $value + */ + public function setParsedContentAttribute($value) + { + $this->attributes['content'] = $value; + } + /** * Get the content rendered as HTML. * From c31c1ea062d7be4bc0214330e4a1e5d130815b73 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 19:03:12 +0930 Subject: [PATCH 05/72] Allow full URLs to be used as the avatar path This is useful for forums integrating with an external website (eg. a WordPress site), so they can reference existing avatars directly. For alternative storage locations (eg. S3) the best practice will still be to store a relative path and then configure an external base "assets URL" (this is not currently possible - TODO). Given this change, I think it would probably make sense to rename the column to `avatar_url` in the upcoming batch of database naming changes - then it can contain either a relative or an absolute URL - @franzliedke do you agree? --- src/Core/User.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Core/User.php b/src/Core/User.php index 32f58e86a..338d38d47 100755 --- a/src/Core/User.php +++ b/src/Core/User.php @@ -323,7 +323,14 @@ class User extends AbstractModel */ public function getAvatarUrlAttribute() { - $urlGenerator = app('Flarum\Forum\UrlGenerator'); + if ($this->avatar_path) { + if (strpos($this->avatar_path, '://') !== false) { + return $this->avatar_path; + } else { + return app('Flarum\Forum\UrlGenerator')->toPath('assets/avatars/'.$this->avatar_path); + } + } + } return $this->avatar_path ? $urlGenerator->toPath('assets/avatars/'.$this->avatar_path) : null; } From 37cf95f94dc3f04f79639586e002ac7096a6b05f Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 19:10:07 +0930 Subject: [PATCH 06/72] Don't include post content in the "basic" serializer Currently all of a post's replies are loaded in full whenever the post is loaded, which is kind of overkill - we really just need to know that they exist (and who posted them) in order to render the "X replied to this" line. --- src/Api/Serializer/DiscussionBasicSerializer.php | 6 +++--- src/Api/Serializer/PostBasicSerializer.php | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Api/Serializer/DiscussionBasicSerializer.php b/src/Api/Serializer/DiscussionBasicSerializer.php index 39bee72ff..99416b10e 100644 --- a/src/Api/Serializer/DiscussionBasicSerializer.php +++ b/src/Api/Serializer/DiscussionBasicSerializer.php @@ -54,7 +54,7 @@ class DiscussionBasicSerializer extends AbstractSerializer */ protected function startPost($discussion) { - return $this->hasOne($discussion, 'Flarum\Api\Serializer\PostBasicSerializer'); + return $this->hasOne($discussion, 'Flarum\Api\Serializer\PostSerializer'); } /** @@ -70,7 +70,7 @@ class DiscussionBasicSerializer extends AbstractSerializer */ protected function lastPost($discussion) { - return $this->hasOne($discussion, 'Flarum\Api\Serializer\PostBasicSerializer'); + return $this->hasOne($discussion, 'Flarum\Api\Serializer\PostSerializer'); } /** @@ -86,6 +86,6 @@ class DiscussionBasicSerializer extends AbstractSerializer */ protected function relevantPosts($discussion) { - return $this->hasMany($discussion, 'Flarum\Api\Serializer\PostBasicSerializer'); + return $this->hasMany($discussion, 'Flarum\Api\Serializer\PostSerializer'); } } diff --git a/src/Api/Serializer/PostBasicSerializer.php b/src/Api/Serializer/PostBasicSerializer.php index 962a7b28b..c5ef11168 100644 --- a/src/Api/Serializer/PostBasicSerializer.php +++ b/src/Api/Serializer/PostBasicSerializer.php @@ -43,12 +43,6 @@ class PostBasicSerializer extends AbstractSerializer 'contentType' => $post->type ]; - if ($post instanceof CommentPost) { - $attributes['contentHtml'] = $post->content_html; - } else { - $attributes['content'] = $post->content; - } - return $attributes; } From 377d439c474dad2f768575028ded0daa6557e4b0 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 19:13:02 +0930 Subject: [PATCH 07/72] =?UTF-8?q?=F0=9F=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/User.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Core/User.php b/src/Core/User.php index 338d38d47..fa4568df7 100755 --- a/src/Core/User.php +++ b/src/Core/User.php @@ -332,9 +332,6 @@ class User extends AbstractModel } } - return $this->avatar_path ? $urlGenerator->toPath('assets/avatars/'.$this->avatar_path) : null; - } - /** * Get the user's locale, falling back to the forum's default if they * haven't set one. From ef89b1f6b18e8a7fa5f59a2990034b54bbb86c57 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 19:44:43 +0930 Subject: [PATCH 08/72] Remove unnecessary else statement --- src/Core/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/User.php b/src/Core/User.php index fa4568df7..4f6238278 100755 --- a/src/Core/User.php +++ b/src/Core/User.php @@ -326,9 +326,9 @@ class User extends AbstractModel if ($this->avatar_path) { if (strpos($this->avatar_path, '://') !== false) { return $this->avatar_path; - } else { - return app('Flarum\Forum\UrlGenerator')->toPath('assets/avatars/'.$this->avatar_path); } + + return app('Flarum\Forum\UrlGenerator')->toPath('assets/avatars/'.$this->avatar_path); } } From f917d1438c1e2fad898b24086b2d3bef877edbc6 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 19:45:31 +0930 Subject: [PATCH 09/72] Use ::class --- src/Core/User.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/User.php b/src/Core/User.php index 4f6238278..dd50ab449 100755 --- a/src/Core/User.php +++ b/src/Core/User.php @@ -29,6 +29,7 @@ use Flarum\Event\UserWasActivated; use Flarum\Event\UserWasDeleted; use Flarum\Event\UserWasRegistered; use Flarum\Event\UserWasRenamed; +use Flarum\Forum\UrlGenerator; use Flarum\Foundation\Application; use Illuminate\Contracts\Hashing\Hasher; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -328,7 +329,7 @@ class User extends AbstractModel return $this->avatar_path; } - return app('Flarum\Forum\UrlGenerator')->toPath('assets/avatars/'.$this->avatar_path); + return app(UrlGenerator::class)->toPath('assets/avatars/'.$this->avatar_path); } } From eb72307a546cf1bd604dc15b93e699a24a37b2c4 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Wed, 20 Sep 2017 16:42:18 +0930 Subject: [PATCH 10/72] User display names (#1246) * Introduce user display names It is not uncommon for forums to be intergrated with sites where users don't have a unique "handle" - they might just have their first name, or a full name, which is not guaranteed to be unique. This commit introduces the concept of "display names" for users. By default display names are the same as usernames, but extensions may override this and set them to something different. The important thing is that all code should use `display_name` whenever intending to output a human-readable name - `username` is reserved for cases where you want to output a unique identifier (which may or may not be human-friendly). The new "GetDisplayName" API is probably sub-optimal, but I didn't worry too much because we can come up with something better in `next-back`. ref #557 * Apply fixes from StyleCI [ci skip] [skip ci] --- js/forum/dist/app.js | 7 +++-- js/forum/src/components/UsersSearchSource.js | 2 +- js/lib/helpers/username.js | 2 +- js/lib/models/User.js | 1 + src/Api/Serializer/PostBasicSerializer.php | 1 - src/Api/Serializer/UserBasicSerializer.php | 5 ++-- .../Command/RequestPasswordResetHandler.php | 2 +- src/Core/Listener/EmailConfirmationMailer.php | 2 +- src/Core/User.php | 11 +++++++ src/Event/GetDisplayName.php | 30 +++++++++++++++++++ 10 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 src/Event/GetDisplayName.php diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js index 16374dc0b..fffd65333 100644 --- a/js/forum/dist/app.js +++ b/js/forum/dist/app.js @@ -28942,7 +28942,9 @@ System.register('flarum/components/UsersSearchSource', ['flarum/helpers/highligh query = query.toLowerCase(); var results = app.store.all('users').filter(function (user) { - return user.username().toLowerCase().substr(0, query.length) === query; + return [user.username(), user.displayName()].some(function (value) { + return value.toLowerCase().substr(0, query.length) === query; + }); }); if (!results.length) return ''; @@ -29534,7 +29536,7 @@ System.register("flarum/helpers/username", [], function (_export, _context) { "use strict"; function username(user) { - var name = user && user.username() || app.translator.trans('core.lib.username.deleted_text'); + var name = user && user.displayName() || app.translator.trans('core.lib.username.deleted_text'); return m( "span", @@ -30533,6 +30535,7 @@ System.register('flarum/models/User', ['flarum/Model', 'flarum/utils/stringToCol babelHelpers.extends(User.prototype, { username: Model.attribute('username'), + displayName: Model.attribute('displayName'), email: Model.attribute('email'), isActivated: Model.attribute('isActivated'), password: Model.attribute('password'), diff --git a/js/forum/src/components/UsersSearchSource.js b/js/forum/src/components/UsersSearchSource.js index f05fbb30d..7f687b811 100644 --- a/js/forum/src/components/UsersSearchSource.js +++ b/js/forum/src/components/UsersSearchSource.js @@ -20,7 +20,7 @@ export default class UsersSearchResults { query = query.toLowerCase(); const results = app.store.all('users') - .filter(user => user.username().toLowerCase().substr(0, query.length) === query); + .filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query)); if (!results.length) return ''; diff --git a/js/lib/helpers/username.js b/js/lib/helpers/username.js index 16e12654f..7e05ca4d6 100644 --- a/js/lib/helpers/username.js +++ b/js/lib/helpers/username.js @@ -6,7 +6,7 @@ * @return {Object} */ export default function username(user) { - const name = (user && user.username()) || app.translator.trans('core.lib.username.deleted_text'); + const name = (user && user.displayName()) || app.translator.trans('core.lib.username.deleted_text'); return {name}; } diff --git a/js/lib/models/User.js b/js/lib/models/User.js index a28173c3b..07d5aaf1b 100644 --- a/js/lib/models/User.js +++ b/js/lib/models/User.js @@ -10,6 +10,7 @@ export default class User extends Model {} Object.assign(User.prototype, { username: Model.attribute('username'), + displayName: Model.attribute('displayName'), email: Model.attribute('email'), isActivated: Model.attribute('isActivated'), password: Model.attribute('password'), diff --git a/src/Api/Serializer/PostBasicSerializer.php b/src/Api/Serializer/PostBasicSerializer.php index c5ef11168..3a79eed84 100644 --- a/src/Api/Serializer/PostBasicSerializer.php +++ b/src/Api/Serializer/PostBasicSerializer.php @@ -12,7 +12,6 @@ namespace Flarum\Api\Serializer; use Flarum\Core\Post; -use Flarum\Core\Post\CommentPost; use InvalidArgumentException; class PostBasicSerializer extends AbstractSerializer diff --git a/src/Api/Serializer/UserBasicSerializer.php b/src/Api/Serializer/UserBasicSerializer.php index 2699c8714..f5919ebf5 100644 --- a/src/Api/Serializer/UserBasicSerializer.php +++ b/src/Api/Serializer/UserBasicSerializer.php @@ -36,8 +36,9 @@ class UserBasicSerializer extends AbstractSerializer } return [ - 'username' => $user->username, - 'avatarUrl' => $user->avatar_url + 'username' => $user->username, + 'displayName' => $user->display_name, + 'avatarUrl' => $user->avatar_url ]; } diff --git a/src/Core/Command/RequestPasswordResetHandler.php b/src/Core/Command/RequestPasswordResetHandler.php index fb484fbda..3318c9882 100644 --- a/src/Core/Command/RequestPasswordResetHandler.php +++ b/src/Core/Command/RequestPasswordResetHandler.php @@ -107,7 +107,7 @@ class RequestPasswordResetHandler $token->save(); $data = [ - '{username}' => $user->username, + '{username}' => $user->display_name, '{url}' => $this->url->toRoute('resetPassword', ['token' => $token->id]), '{forum}' => $this->settings->get('forum_title'), ]; diff --git a/src/Core/Listener/EmailConfirmationMailer.php b/src/Core/Listener/EmailConfirmationMailer.php index 295d45d7c..6c7583cad 100755 --- a/src/Core/Listener/EmailConfirmationMailer.php +++ b/src/Core/Listener/EmailConfirmationMailer.php @@ -130,7 +130,7 @@ class EmailConfirmationMailer $token = $this->generateToken($user, $email); return [ - '{username}' => $user->username, + '{username}' => $user->display_name, '{url}' => $this->url->toRoute('confirmEmail', ['token' => $token->id]), '{forum}' => $this->settings->get('forum_title') ]; diff --git a/src/Core/User.php b/src/Core/User.php index dd50ab449..e8325b0e5 100755 --- a/src/Core/User.php +++ b/src/Core/User.php @@ -18,6 +18,7 @@ use Flarum\Core\Support\ScopeVisibilityTrait; use Flarum\Database\AbstractModel; use Flarum\Event\CheckUserPassword; use Flarum\Event\ConfigureUserPreferences; +use Flarum\Event\GetDisplayName; use Flarum\Event\PostWasDeleted; use Flarum\Event\PrepareUserGroups; use Flarum\Event\UserAvatarWasChanged; @@ -333,6 +334,16 @@ class User extends AbstractModel } } + /** + * Get the user's display name. + * + * @return string + */ + public function getDisplayNameAttribute() + { + return static::$dispatcher->until(new GetDisplayName($this)) ?: $this->username; + } + /** * Get the user's locale, falling back to the forum's default if they * haven't set one. diff --git a/src/Event/GetDisplayName.php b/src/Event/GetDisplayName.php new file mode 100644 index 000000000..71fbe6ba4 --- /dev/null +++ b/src/Event/GetDisplayName.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Event; + +use Flarum\Core\User; + +class GetDisplayName +{ + /** + * @var User + */ + public $user; + + /** + * @param User $user + */ + public function __construct(User $user) + { + $this->user = $user; + } +} From 02377663cebff5689161eb7c093c465c90aa50e5 Mon Sep 17 00:00:00 2001 From: Tobias Weichart Date: Wed, 4 Oct 2017 08:38:36 +0200 Subject: [PATCH 11/72] minor change for getting the path * should be used via x_path() methods + parameter --- src/Asset/LessCompiler.php | 2 +- src/Database/MigrationCreator.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Asset/LessCompiler.php b/src/Asset/LessCompiler.php index 382a1b210..d498b08d9 100644 --- a/src/Asset/LessCompiler.php +++ b/src/Asset/LessCompiler.php @@ -49,7 +49,7 @@ class LessCompiler extends RevisionCompiler 'compress' => true, 'cache_dir' => $this->cachePath, 'import_dirs' => [ - base_path().'/vendor/components/font-awesome/less' => '', + base_path('vendor/components/font-awesome/less') => '', ], ]); diff --git a/src/Database/MigrationCreator.php b/src/Database/MigrationCreator.php index b3a912206..53fa22bff 100755 --- a/src/Database/MigrationCreator.php +++ b/src/Database/MigrationCreator.php @@ -107,7 +107,7 @@ class MigrationCreator */ protected function getMigrationPath($extension) { - $parent = $extension ? public_path().'/extensions/'.$extension : __DIR__.'/../..'; + $parent = $extension ? public_path('extensions/'.$extension) : __DIR__.'/../..'; return $parent.'/migrations'; } From 19d15d43023353f26042f6b6c5d1ebcc505f6b9d Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 5 Oct 2017 11:43:12 +1030 Subject: [PATCH 12/72] Use display name as document title --- js/forum/src/components/UserPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/forum/src/components/UserPage.js b/js/forum/src/components/UserPage.js index e7bebdd34..e8679ea58 100644 --- a/js/forum/src/components/UserPage.js +++ b/js/forum/src/components/UserPage.js @@ -72,7 +72,7 @@ export default class UserPage extends Page { show(user) { this.user = user; - app.setTitle(user.username()); + app.setTitle(user.displayName()); m.redraw(); } From ea4d889b76ba03ddf55a77d6333c0a8352b9b854 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 5 Oct 2017 11:48:10 +1030 Subject: [PATCH 13/72] Fix post contentHtml sometimes breaking script parsing <\/script> tags in post content would sometimes be recognized as actually ending the script tag, even when escaped (not exactly sure why). This is fixed by encoding the < > characters in unicode. --- views/app.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/app.blade.php b/views/app.blade.php index 2f98cb715..198fde143 100644 --- a/views/app.blade.php +++ b/views/app.blade.php @@ -49,7 +49,7 @@ if (module.default) module.default(app); } - app.boot({!! json_encode($payload) !!}); + app.boot({!! json_encode($payload, JSON_HEX_TAG) !!}); @if (! $debug) } catch (e) { window.location += (window.location.search ? '&' : '?') + 'nojs=1'; From 8ccfb1aac6171c314ecfaa84491e40346c36ee45 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 5 Oct 2017 12:25:30 +1030 Subject: [PATCH 14/72] Use a constant instead of a property --- src/Http/Middleware/AuthenticateWithHeader.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Http/Middleware/AuthenticateWithHeader.php b/src/Http/Middleware/AuthenticateWithHeader.php index 9b34b11dc..1bf1fd51b 100644 --- a/src/Http/Middleware/AuthenticateWithHeader.php +++ b/src/Http/Middleware/AuthenticateWithHeader.php @@ -20,10 +20,7 @@ use Zend\Stratigility\MiddlewareInterface; class AuthenticateWithHeader implements MiddlewareInterface { - /** - * @var string - */ - protected $prefix = 'Token '; + const TOKEN_PREFIX = 'Token '; /** * {@inheritdoc} @@ -34,8 +31,8 @@ class AuthenticateWithHeader implements MiddlewareInterface $parts = explode(';', $headerLine); - if (isset($parts[0]) && starts_with($parts[0], $this->prefix)) { - $id = substr($parts[0], strlen($this->prefix)); + if (isset($parts[0]) && starts_with($parts[0], self::TOKEN_PREFIX)) { + $id = substr($parts[0], strlen(self::TOKEN_PREFIX)); if (isset($parts[1])) { if (ApiKey::find($id)) { From 096e552c74ce88dfc1f5da45d5bbea6435a0c7a9 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 5 Oct 2017 12:26:05 +1030 Subject: [PATCH 15/72] Add the ApiKey model as a request attribute --- src/Http/Middleware/AuthenticateWithHeader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Http/Middleware/AuthenticateWithHeader.php b/src/Http/Middleware/AuthenticateWithHeader.php index 1bf1fd51b..dfb3f8e9c 100644 --- a/src/Http/Middleware/AuthenticateWithHeader.php +++ b/src/Http/Middleware/AuthenticateWithHeader.php @@ -35,9 +35,10 @@ class AuthenticateWithHeader implements MiddlewareInterface $id = substr($parts[0], strlen(self::TOKEN_PREFIX)); if (isset($parts[1])) { - if (ApiKey::find($id)) { + if ($key = ApiKey::find($id)) { $actor = $this->getUser($parts[1]); + $request = $request->withAttribute('apiKey', $key); $request = $request->withAttribute('bypassFloodgate', true); } } elseif ($token = AccessToken::find($id)) { From 87bf84ef6e1b04d1fa248f7298fb96981251319e Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 5 Oct 2017 12:39:40 +1030 Subject: [PATCH 16/72] Allow configuring cookie attributes I decided to put this in config.php because if cookie settings were to be stored in the database and configured via admin UI, entering incorrect settings could cause the admin session to be destroyed, requiring manual database intervention to fix. But it's a good prompt for discussion as to which kind of settings belong in config.php vs the database. Thoughts? --- src/Http/CookieFactory.php | 35 +++++++++++++++++++++++++--- src/Http/Middleware/StartSession.php | 7 +++--- src/Http/Rememberer.php | 9 ++++--- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Http/CookieFactory.php b/src/Http/CookieFactory.php index 868db278c..aacaff7f5 100644 --- a/src/Http/CookieFactory.php +++ b/src/Http/CookieFactory.php @@ -45,7 +45,7 @@ class CookieFactory // Parse the forum's base URL so that we can determine the optimal cookie settings $url = parse_url(rtrim($this->app->url(), '/')); - $cookie = SetCookie::create($name, $value); + $cookie = SetCookie::create($this->getName($name), $value); // Make sure we send both the MaxAge and Expires parameters (the former // is not supported by all browser versions) @@ -55,9 +55,38 @@ class CookieFactory ->withExpires(time() + $maxAge); } + if ($domain = $this->app->config('cookie.domain')) { + $cookie = $cookie->withDomain($domain); + } + + $path = $this->app->config('cookie.path', array_get($url, 'path') ?: '/'); + $secure = $this->app->config('cookie.secure', array_get($url, 'scheme') === 'https'); + return $cookie - ->withPath(array_get($url, 'path') ?: '/') - ->withSecure(array_get($url, 'scheme') === 'https') + ->withPath($path) + ->withSecure($secure) ->withHttpOnly(true); } + + /** + * Make an expired cookie instance. + * + * @param string $name + * @return \Dflydev\FigCookies\SetCookie + */ + public function expire($name) + { + return $this->make($name)->expire(); + } + + /** + * Get a cookie name. + * + * @param string $name + * @return string + */ + public function getName($name) + { + return $this->app->config('cookie.name', 'flarum').'_'.$name; + } } diff --git a/src/Http/Middleware/StartSession.php b/src/Http/Middleware/StartSession.php index 9c6060bfc..bf714c99e 100644 --- a/src/Http/Middleware/StartSession.php +++ b/src/Http/Middleware/StartSession.php @@ -22,13 +22,14 @@ use Zend\Stratigility\MiddlewareInterface; class StartSession implements MiddlewareInterface { + const COOKIE_NAME = 'session'; + /** * @var CookieFactory */ protected $cookie; /** - * Rememberer constructor. * @param CookieFactory $cookie */ public function __construct(CookieFactory $cookie) @@ -56,7 +57,7 @@ class StartSession implements MiddlewareInterface { $session = new Session; - $session->setName('flarum_session'); + $session->setName($this->cookie->getName(self::COOKIE_NAME)); $session->start(); if (! $session->has('csrf_token')) { @@ -79,7 +80,7 @@ class StartSession implements MiddlewareInterface { return FigResponseCookies::set( $response, - $this->cookie->make($session->getName(), $session->getId()) + $this->cookie->make(self::COOKIE_NAME, $session->getId()) ); } } diff --git a/src/Http/Rememberer.php b/src/Http/Rememberer.php index a014c99d9..d331d796c 100644 --- a/src/Http/Rememberer.php +++ b/src/Http/Rememberer.php @@ -16,7 +16,7 @@ use Psr\Http\Message\ResponseInterface; class Rememberer { - protected $cookieName = 'flarum_remember'; + const COOKIE_NAME = 'remember'; /** * @var CookieFactory @@ -43,7 +43,7 @@ class Rememberer return FigResponseCookies::set( $response, - $this->cookie->make($this->cookieName, $token->id, $lifetime) + $this->cookie->make(self::COOKIE_NAME, $token->id, $lifetime) ); } @@ -56,6 +56,9 @@ class Rememberer public function forget(ResponseInterface $response) { - return FigResponseCookies::expire($response, $this->cookieName); + return FigResponseCookies::set( + $response, + $this->cookie->expire(self::COOKIE_NAME) + ); } } From 2dbcfe02d85c03e2fe212e477a0d5d3bb66fd4b5 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 17:39:27 +1030 Subject: [PATCH 17/72] Consolidate avatar uploading, allow avatarUrl to be used when updating user --- src/Core/AvatarUploader.php | 55 ++++++++++++++++++ src/Core/Command/DeleteAvatarHandler.php | 18 +++--- src/Core/Command/EditUserHandler.php | 37 +++++++++++- src/Core/Command/RegisterUserHandler.php | 59 +++++-------------- src/Core/Command/UploadAvatarHandler.php | 72 ++++++++---------------- src/Core/CoreServiceProvider.php | 10 +--- src/Event/AvatarWillBeSaved.php | 13 +++-- 7 files changed, 143 insertions(+), 121 deletions(-) create mode 100755 src/Core/AvatarUploader.php diff --git a/src/Core/AvatarUploader.php b/src/Core/AvatarUploader.php new file mode 100755 index 000000000..3d7c018e7 --- /dev/null +++ b/src/Core/AvatarUploader.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Core; + +use Intervention\Image\Image; +use League\Flysystem\FilesystemInterface; +use Illuminate\Support\Str; + +class AvatarUploader +{ + protected $uploadDir; + + public function __construct(FilesystemInterface $uploadDir) + { + $this->uploadDir = $uploadDir; + } + + public function upload(User $user, Image $image) + { + if (extension_loaded('exif')) { + $image->orientate(); + } + + $encodedImage = $image->fit(100, 100)->encode('png'); + + $avatarPath = Str::quickRandom().'.png'; + + $this->remove($user); + $user->changeAvatarPath($avatarPath); + + $this->uploadDir->put($avatarPath, $encodedImage); + } + + public function remove(User $user) + { + $avatarPath = $user->avatar_path; + + $user->afterSave(function () use ($avatarPath) { + if ($this->uploadDir->has($avatarPath)) { + $this->uploadDir->delete($avatarPath); + } + }); + + $user->changeAvatarPath(null); + } +} diff --git a/src/Core/Command/DeleteAvatarHandler.php b/src/Core/Command/DeleteAvatarHandler.php index 8efd972c7..2a7cb1638 100644 --- a/src/Core/Command/DeleteAvatarHandler.php +++ b/src/Core/Command/DeleteAvatarHandler.php @@ -12,6 +12,7 @@ namespace Flarum\Core\Command; use Flarum\Core\Access\AssertPermissionTrait; +use Flarum\Core\AvatarUploader; use Flarum\Core\Exception\PermissionDeniedException; use Flarum\Core\Repository\UserRepository; use Flarum\Core\Support\DispatchEventsTrait; @@ -30,20 +31,20 @@ class DeleteAvatarHandler protected $users; /** - * @var FilesystemInterface + * @var AvatarUploader */ - protected $uploadDir; + protected $uploader; /** * @param Dispatcher $events * @param UserRepository $users - * @param FilesystemInterface $uploadDir + * @param AvatarUploader $uploader */ - public function __construct(Dispatcher $events, UserRepository $users, FilesystemInterface $uploadDir) + public function __construct(Dispatcher $events, UserRepository $users, AvatarUploader $uploader) { $this->events = $events; $this->users = $users; - $this->uploadDir = $uploadDir; + $this->uploader = $uploader; } /** @@ -61,8 +62,7 @@ class DeleteAvatarHandler $this->assertCan($actor, 'edit', $user); } - $avatarPath = $user->avatar_path; - $user->changeAvatarPath(null); + $this->uploader->remove($user); $this->events->fire( new AvatarWillBeDeleted($user, $actor) @@ -70,10 +70,6 @@ class DeleteAvatarHandler $user->save(); - if ($this->uploadDir->has($avatarPath)) { - $this->uploadDir->delete($avatarPath); - } - $this->dispatchEventsFor($user, $actor); return $user; diff --git a/src/Core/Command/EditUserHandler.php b/src/Core/Command/EditUserHandler.php index d42acdfc9..d5d4bae1b 100644 --- a/src/Core/Command/EditUserHandler.php +++ b/src/Core/Command/EditUserHandler.php @@ -11,7 +11,9 @@ namespace Flarum\Core\Command; +use Exception; use Flarum\Core\Access\AssertPermissionTrait; +use Flarum\Core\AvatarUploader; use Flarum\Core\Repository\UserRepository; use Flarum\Core\Support\DispatchEventsTrait; use Flarum\Core\User; @@ -19,6 +21,9 @@ use Flarum\Core\Validator\UserValidator; use Flarum\Event\UserGroupsWereChanged; use Flarum\Event\UserWillBeSaved; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Contracts\Validation\Factory; +use Illuminate\Contracts\Validation\ValidationException; +use Intervention\Image\ImageManager; class EditUserHandler { @@ -35,16 +40,30 @@ class EditUserHandler */ protected $validator; + /** + * @var AvatarUploader + */ + protected $avatarUploader; + + /** + * @var Factory + */ + private $validatorFactory; + /** * @param Dispatcher $events * @param UserRepository $users * @param UserValidator $validator + * @param AvatarUploader $avatarUploader + * @param Factory $validatorFactory */ - public function __construct(Dispatcher $events, UserRepository $users, UserValidator $validator) + public function __construct(Dispatcher $events, UserRepository $users, UserValidator $validator, AvatarUploader $avatarUploader, Factory $validatorFactory) { $this->events = $events; $this->users = $users; $this->validator = $validator; + $this->avatarUploader = $avatarUploader; + $this->validatorFactory = $validatorFactory; } /** @@ -135,6 +154,22 @@ class EditUserHandler }); } + if ($avatarUrl = array_get($attributes, 'avatarUrl')) { + $validation = $this->validatorFactory->make(compact('avatarUrl'), ['avatarUrl' => 'url']); + + if ($validation->fails()) { + throw new ValidationException($validation); + } + + try { + $image = (new ImageManager)->make($avatarUrl); + + $this->avatarUploader->upload($user, $image); + } catch (Exception $e) { + // + } + } + $this->events->fire( new UserWillBeSaved($user, $actor, $data) ); diff --git a/src/Core/Command/RegisterUserHandler.php b/src/Core/Command/RegisterUserHandler.php index c7daf38fa..f57492548 100644 --- a/src/Core/Command/RegisterUserHandler.php +++ b/src/Core/Command/RegisterUserHandler.php @@ -14,22 +14,17 @@ namespace Flarum\Core\Command; use Exception; use Flarum\Core\Access\AssertPermissionTrait; use Flarum\Core\AuthToken; +use Flarum\Core\AvatarUploader; use Flarum\Core\Exception\PermissionDeniedException; use Flarum\Core\Support\DispatchEventsTrait; use Flarum\Core\User; use Flarum\Core\Validator\UserValidator; use Flarum\Event\UserWillBeSaved; -use Flarum\Foundation\Application; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Validation\Factory; use Illuminate\Contracts\Validation\ValidationException; -use Illuminate\Support\Str; use Intervention\Image\ImageManager; -use League\Flysystem\Adapter\Local; -use League\Flysystem\Filesystem; -use League\Flysystem\FilesystemInterface; -use League\Flysystem\MountManager; class RegisterUserHandler { @@ -47,14 +42,9 @@ class RegisterUserHandler protected $validator; /** - * @var Application + * @var AvatarUploader */ - protected $app; - - /** - * @var FilesystemInterface - */ - protected $uploadDir; + protected $avatarUploader; /** * @var Factory @@ -65,17 +55,15 @@ class RegisterUserHandler * @param Dispatcher $events * @param SettingsRepositoryInterface $settings * @param UserValidator $validator - * @param Application $app - * @param FilesystemInterface $uploadDir + * @param AvatarUploader $avatarUploader * @param Factory $validatorFactory */ - public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $validator, Application $app, FilesystemInterface $uploadDir, Factory $validatorFactory) + public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $validator, AvatarUploader $avatarUploader, Factory $validatorFactory) { $this->events = $events; $this->settings = $settings; $this->validator = $validator; - $this->app = $app; - $this->uploadDir = $uploadDir; + $this->avatarUploader = $avatarUploader; $this->validatorFactory = $validatorFactory; } @@ -130,12 +118,6 @@ class RegisterUserHandler $user->activate(); } - $this->events->fire( - new UserWillBeSaved($user, $actor, $data) - ); - - $this->validator->assertValid(array_merge($user->getAttributes(), compact('password'))); - if ($avatarUrl = array_get($data, 'attributes.avatarUrl')) { $validation = $this->validatorFactory->make(compact('avatarUrl'), ['avatarUrl' => 'url']); @@ -144,12 +126,20 @@ class RegisterUserHandler } try { - $this->saveAvatarFromUrl($user, $avatarUrl); + $image = (new ImageManager)->make($avatarUrl); + + $this->avatarUploader->upload($user, $avatarUrl); } catch (Exception $e) { // } } + $this->events->fire( + new UserWillBeSaved($user, $actor, $data) + ); + + $this->validator->assertValid(array_merge($user->getAttributes(), compact('password'))); + $user->save(); if (isset($token)) { @@ -160,23 +150,4 @@ class RegisterUserHandler return $user; } - - private function saveAvatarFromUrl(User $user, $url) - { - $tmpFile = tempnam($this->app->storagePath().'/tmp', 'avatar'); - - $manager = new ImageManager; - $manager->make($url)->fit(100, 100)->save($tmpFile); - - $mount = new MountManager([ - 'source' => new Filesystem(new Local(pathinfo($tmpFile, PATHINFO_DIRNAME))), - 'target' => $this->uploadDir, - ]); - - $uploadName = Str::lower(Str::quickRandom()).'.png'; - - $user->changeAvatarPath($uploadName); - - $mount->move('source://'.pathinfo($tmpFile, PATHINFO_BASENAME), "target://$uploadName"); - } } diff --git a/src/Core/Command/UploadAvatarHandler.php b/src/Core/Command/UploadAvatarHandler.php index de451a074..02e655bd6 100644 --- a/src/Core/Command/UploadAvatarHandler.php +++ b/src/Core/Command/UploadAvatarHandler.php @@ -13,18 +13,14 @@ namespace Flarum\Core\Command; use Exception; use Flarum\Core\Access\AssertPermissionTrait; +use Flarum\Core\AvatarUploader; use Flarum\Core\Repository\UserRepository; use Flarum\Core\Support\DispatchEventsTrait; use Flarum\Core\Validator\AvatarValidator; use Flarum\Event\AvatarWillBeSaved; use Flarum\Foundation\Application; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Support\Str; use Intervention\Image\ImageManager; -use League\Flysystem\Adapter\Local; -use League\Flysystem\Filesystem; -use League\Flysystem\FilesystemInterface; -use League\Flysystem\MountManager; use Symfony\Component\HttpFoundation\File\UploadedFile; class UploadAvatarHandler @@ -37,16 +33,16 @@ class UploadAvatarHandler */ protected $users; - /** - * @var FilesystemInterface - */ - protected $uploadDir; - /** * @var Application */ protected $app; + /** + * @var AvatarUploader + */ + protected $uploader; + /** * @var AvatarValidator */ @@ -55,16 +51,16 @@ class UploadAvatarHandler /** * @param Dispatcher $events * @param UserRepository $users - * @param FilesystemInterface $uploadDir * @param Application $app + * @param AvatarUploader $uploader * @param AvatarValidator $validator */ - public function __construct(Dispatcher $events, UserRepository $users, FilesystemInterface $uploadDir, Application $app, AvatarValidator $validator) + public function __construct(Dispatcher $events, UserRepository $users, Application $app, AvatarUploader $uploader, AvatarValidator $validator) { $this->events = $events; $this->users = $users; - $this->uploadDir = $uploadDir; $this->app = $app; + $this->uploader = $uploader; $this->validator = $validator; } @@ -83,60 +79,36 @@ class UploadAvatarHandler $this->assertCan($actor, 'edit', $user); } + $file = $command->file; + $tmpFile = tempnam($this->app->storagePath().'/tmp', 'avatar'); - $command->file->moveTo($tmpFile); + $file->moveTo($tmpFile); try { $file = new UploadedFile( $tmpFile, - $command->file->getClientFilename(), - $command->file->getClientMediaType(), - $command->file->getSize(), - $command->file->getError(), + $file->getClientFilename(), + $file->getClientMediaType(), + $file->getSize(), + $file->getError(), true ); $this->validator->assertValid(['avatar' => $file]); - $manager = new ImageManager; - - // Explicitly tell Intervention to encode the image as PNG (instead of having to guess from the extension) - // Read exif data to orientate avatar only if EXIF extension is enabled - if (extension_loaded('exif')) { - $encodedImage = $manager->make($tmpFile)->orientate()->fit(100, 100)->encode('png', 100); - } else { - $encodedImage = $manager->make($tmpFile)->fit(100, 100)->encode('png', 100); - } - file_put_contents($tmpFile, $encodedImage); + $image = (new ImageManager)->make($tmpFile); $this->events->fire( - new AvatarWillBeSaved($user, $actor, $tmpFile) + new AvatarWillBeSaved($user, $actor, $image) ); - $mount = new MountManager([ - 'source' => new Filesystem(new Local(pathinfo($tmpFile, PATHINFO_DIRNAME))), - 'target' => $this->uploadDir, - ]); - - if ($user->avatar_path && $mount->has($file = "target://$user->avatar_path")) { - $mount->delete($file); - } - - $uploadName = Str::lower(Str::quickRandom()).'.png'; - - $user->changeAvatarPath($uploadName); - - $mount->move('source://'.pathinfo($tmpFile, PATHINFO_BASENAME), "target://$uploadName"); + $this->uploader->upload($user, $image); $user->save(); - - $this->dispatchEventsFor($user, $actor); - - return $user; - } catch (Exception $e) { + } finally { @unlink($tmpFile); - - throw $e; } + + return $user; } } diff --git a/src/Core/CoreServiceProvider.php b/src/Core/CoreServiceProvider.php index 551cdcfda..713e5758e 100644 --- a/src/Core/CoreServiceProvider.php +++ b/src/Core/CoreServiceProvider.php @@ -49,15 +49,7 @@ class CoreServiceProvider extends AbstractServiceProvider return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('flarum-avatars')->getDriver(); }; - $this->app->when('Flarum\Core\Command\UploadAvatarHandler') - ->needs('League\Flysystem\FilesystemInterface') - ->give($avatarsFilesystem); - - $this->app->when('Flarum\Core\Command\DeleteAvatarHandler') - ->needs('League\Flysystem\FilesystemInterface') - ->give($avatarsFilesystem); - - $this->app->when('Flarum\Core\Command\RegisterUserHandler') + $this->app->when('Flarum\Core\AvatarUploader') ->needs('League\Flysystem\FilesystemInterface') ->give($avatarsFilesystem); } diff --git a/src/Event/AvatarWillBeSaved.php b/src/Event/AvatarWillBeSaved.php index f4e52bb65..0c52399cc 100644 --- a/src/Event/AvatarWillBeSaved.php +++ b/src/Event/AvatarWillBeSaved.php @@ -12,6 +12,7 @@ namespace Flarum\Event; use Flarum\Core\User; +use Intervention\Image\Image; class AvatarWillBeSaved { @@ -30,21 +31,21 @@ class AvatarWillBeSaved public $actor; /** - * The path to the avatar that will be saved. + * The image that will be saved. * - * @var string + * @var Image */ - public $path; + public $image; /** * @param User $user The user whose avatar will be saved. * @param User $actor The user performing the action. - * @param string $path The path to the avatar that will be saved. + * @param Image $image The image that will be saved. */ - public function __construct(User $user, User $actor, $path) + public function __construct(User $user, User $actor, Image $image) { $this->user = $user; $this->actor = $actor; - $this->path = $path; + $this->image = $image; } } From 3b1f8771c49aff952f9988e901fe6b732d131cb6 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 17:51:30 +1030 Subject: [PATCH 18/72] No need to set a remember cookie if only logging in for session --- src/Forum/Controller/LogInController.php | 4 +++- src/Http/Rememberer.php | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Forum/Controller/LogInController.php b/src/Forum/Controller/LogInController.php index f131150f9..8fc2fd879 100644 --- a/src/Forum/Controller/LogInController.php +++ b/src/Forum/Controller/LogInController.php @@ -81,7 +81,9 @@ class LogInController implements ControllerInterface event(new UserLoggedIn($this->users->findOrFail($data->userId), $token)); - $response = $this->rememberer->remember($response, $token, ! array_get($body, 'remember')); + if (array_get($body, 'remember')) { + $response = $this->rememberer->remember($response, $token); + } } return $response; diff --git a/src/Http/Rememberer.php b/src/Http/Rememberer.php index d331d796c..697cfe243 100644 --- a/src/Http/Rememberer.php +++ b/src/Http/Rememberer.php @@ -24,7 +24,6 @@ class Rememberer protected $cookie; /** - * Rememberer constructor. * @param CookieFactory $cookie */ public function __construct(CookieFactory $cookie) @@ -32,18 +31,16 @@ class Rememberer $this->cookie = $cookie; } - public function remember(ResponseInterface $response, AccessToken $token, $session = false) + public function remember(ResponseInterface $response, AccessToken $token) { $lifetime = null; - if (! $session) { - $token->lifetime = $lifetime = 5 * 365 * 24 * 60 * 60; // 5 years - $token->save(); - } + $token->lifetime = 5 * 365 * 24 * 60 * 60; // 5 years + $token->save(); return FigResponseCookies::set( $response, - $this->cookie->make(self::COOKIE_NAME, $token->id, $lifetime) + $this->cookie->make(self::COOKIE_NAME, $token->id, $token->lifetime) ); } From bedf7107683b92d1210b45852b8a3ceb8041e3ad Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 19:17:59 +1030 Subject: [PATCH 19/72] Fix variable --- src/Core/Command/RegisterUserHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Command/RegisterUserHandler.php b/src/Core/Command/RegisterUserHandler.php index f57492548..809b5b38e 100644 --- a/src/Core/Command/RegisterUserHandler.php +++ b/src/Core/Command/RegisterUserHandler.php @@ -128,7 +128,7 @@ class RegisterUserHandler try { $image = (new ImageManager)->make($avatarUrl); - $this->avatarUploader->upload($user, $avatarUrl); + $this->avatarUploader->upload($user, $image); } catch (Exception $e) { // } From cb92deee982c52ed605b3e30620700849d0d44c2 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Sat, 7 Oct 2017 11:20:38 +0200 Subject: [PATCH 20/72] Fix namespace imports --- src/Core/AvatarUploader.php | 2 +- src/Core/Command/DeleteAvatarHandler.php | 1 - src/Core/Command/UploadAvatarHandler.php | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Core/AvatarUploader.php b/src/Core/AvatarUploader.php index 3d7c018e7..394a966e6 100755 --- a/src/Core/AvatarUploader.php +++ b/src/Core/AvatarUploader.php @@ -11,9 +11,9 @@ namespace Flarum\Core; +use Illuminate\Support\Str; use Intervention\Image\Image; use League\Flysystem\FilesystemInterface; -use Illuminate\Support\Str; class AvatarUploader { diff --git a/src/Core/Command/DeleteAvatarHandler.php b/src/Core/Command/DeleteAvatarHandler.php index 2a7cb1638..2e735582d 100644 --- a/src/Core/Command/DeleteAvatarHandler.php +++ b/src/Core/Command/DeleteAvatarHandler.php @@ -18,7 +18,6 @@ use Flarum\Core\Repository\UserRepository; use Flarum\Core\Support\DispatchEventsTrait; use Flarum\Event\AvatarWillBeDeleted; use Illuminate\Contracts\Events\Dispatcher; -use League\Flysystem\FilesystemInterface; class DeleteAvatarHandler { diff --git a/src/Core/Command/UploadAvatarHandler.php b/src/Core/Command/UploadAvatarHandler.php index 02e655bd6..0750ff0b7 100644 --- a/src/Core/Command/UploadAvatarHandler.php +++ b/src/Core/Command/UploadAvatarHandler.php @@ -11,7 +11,6 @@ namespace Flarum\Core\Command; -use Exception; use Flarum\Core\Access\AssertPermissionTrait; use Flarum\Core\AvatarUploader; use Flarum\Core\Repository\UserRepository; From 06aa37d2fd0446d62501fc5ac052d7c001462c86 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 20:10:50 +1030 Subject: [PATCH 21/72] Use display names in avatars --- js/lib/helpers/avatar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/helpers/avatar.js b/js/lib/helpers/avatar.js index c9174c7fb..ef47173b7 100644 --- a/js/lib/helpers/avatar.js +++ b/js/lib/helpers/avatar.js @@ -19,7 +19,7 @@ export default function avatar(user, attrs = {}) { // uploaded image, or the first letter of their username if they haven't // uploaded one. if (user) { - const username = user.username() || '?'; + const username = user.displayName() || '?'; const avatarUrl = user.avatarUrl(); if (hasTitle) attrs.title = attrs.title || username; From 6a10b4484f2c08ceeaf11c5f880ad12a8b1c3327 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 20:12:15 +1030 Subject: [PATCH 22/72] Recompile JS --- js/admin/dist/app.js | 5 +++-- js/forum/dist/app.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js/admin/dist/app.js b/js/admin/dist/app.js index 16a08b91b..b8f9c3745 100644 --- a/js/admin/dist/app.js +++ b/js/admin/dist/app.js @@ -21364,7 +21364,7 @@ System.register('flarum/helpers/avatar', [], function (_export, _context) { // uploaded image, or the first letter of their username if they haven't // uploaded one. if (user) { - var username = user.username() || '?'; + var username = user.displayName() || '?'; var avatarUrl = user.avatarUrl(); if (hasTitle) attrs.title = attrs.title || username; @@ -21614,7 +21614,7 @@ System.register("flarum/helpers/username", [], function (_export, _context) { "use strict"; function username(user) { - var name = user && user.username() || app.translator.trans('core.lib.username.deleted_text'); + var name = user && user.displayName() || app.translator.trans('core.lib.username.deleted_text'); return m( "span", @@ -22445,6 +22445,7 @@ System.register('flarum/models/User', ['flarum/Model', 'flarum/utils/stringToCol babelHelpers.extends(User.prototype, { username: Model.attribute('username'), + displayName: Model.attribute('displayName'), email: Model.attribute('email'), isActivated: Model.attribute('isActivated'), password: Model.attribute('password'), diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js index fffd65333..8a51d8577 100644 --- a/js/forum/dist/app.js +++ b/js/forum/dist/app.js @@ -28828,7 +28828,7 @@ System.register('flarum/components/UserPage', ['flarum/components/Page', 'flarum value: function show(user) { this.user = user; - app.setTitle(user.username()); + app.setTitle(user.displayName()); m.redraw(); } @@ -29286,7 +29286,7 @@ System.register('flarum/helpers/avatar', [], function (_export, _context) { // uploaded image, or the first letter of their username if they haven't // uploaded one. if (user) { - var username = user.username() || '?'; + var username = user.displayName() || '?'; var avatarUrl = user.avatarUrl(); if (hasTitle) attrs.title = attrs.title || username; From 5a43f915cbe1c9c3210c559d2db9d5f3857c8ed8 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sat, 7 Oct 2017 20:28:15 +1030 Subject: [PATCH 23/72] Let avatarUrl attribute delete the avatar as well --- src/Core/Command/EditUserHandler.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Core/Command/EditUserHandler.php b/src/Core/Command/EditUserHandler.php index d5d4bae1b..0b366dcec 100644 --- a/src/Core/Command/EditUserHandler.php +++ b/src/Core/Command/EditUserHandler.php @@ -168,6 +168,8 @@ class EditUserHandler } catch (Exception $e) { // } + } elseif (array_key_exists('avatarUrl', $attributes)) { + $this->avatarUploader->remove($user); } $this->events->fire( From 42ecee42a196d3e336ae6c0ac5c8a15c33ed51cc Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sun, 8 Oct 2017 08:59:18 +1030 Subject: [PATCH 24/72] Make sure components receive all children properly --- js/lib/utils/patchMithril.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/utils/patchMithril.js b/js/lib/utils/patchMithril.js index 672b2e39e..8935b2e79 100644 --- a/js/lib/utils/patchMithril.js +++ b/js/lib/utils/patchMithril.js @@ -5,7 +5,7 @@ export default function patchMithril(global) { const m = function(comp, ...args) { if (comp.prototype && comp.prototype instanceof Component) { - return comp.component(...args); + return comp.component(args[0], args.slice(1)); } const node = mo.apply(this, arguments); From f3b4d3558794b512a9afa87ace759c73eaa34ac4 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Sun, 8 Oct 2017 08:59:54 +1030 Subject: [PATCH 25/72] Fix extractText breaking in some cases --- js/lib/utils/extractText.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/utils/extractText.js b/js/lib/utils/extractText.js index faa6bd048..13d55862e 100644 --- a/js/lib/utils/extractText.js +++ b/js/lib/utils/extractText.js @@ -7,7 +7,7 @@ export default function extractText(vdom) { if (vdom instanceof Array) { return vdom.map(element => extractText(element)).join(''); - } else if (typeof vdom === 'object') { + } else if (typeof vdom === 'object' && vdom !== null) { return extractText(vdom.children); } else { return vdom; From 123c8bb73d4ad2c9894622f9a99a0b2fb9c7abe0 Mon Sep 17 00:00:00 2001 From: Joseph Date: Sun, 22 Oct 2017 13:37:08 -0700 Subject: [PATCH 26/72] Add drag and drop avatar uploading --- js/forum/src/components/AvatarEditor.js | 100 +++++++++++++++++++----- less/forum/AvatarEditor.less | 5 +- 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/js/forum/src/components/AvatarEditor.js b/js/forum/src/components/AvatarEditor.js index 8d7f6c525..869904b33 100644 --- a/js/forum/src/components/AvatarEditor.js +++ b/js/forum/src/components/AvatarEditor.js @@ -23,6 +23,13 @@ export default class AvatarEditor extends Component { * @type {Boolean} */ this.loading = false; + + /** + * Whether or not an image has been dragged over the dropzone. + * + * @type {Boolean} + */ + this.isDraggedOver = false; } static initProps(props) { @@ -35,12 +42,17 @@ export default class AvatarEditor extends Component { const user = this.props.user; return ( -
+
{avatar(user)} + onclick={this.quickUpload.bind(this)} + ondragover={this.enableDragover.bind(this)} + ondragenter={this.enableDragover.bind(this)} + ondragleave={this.disableDragover.bind(this)} + ondragend={this.disableDragover.bind(this)} + ondrop={this.dropUpload.bind(this)}> {this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
    @@ -62,7 +74,7 @@ export default class AvatarEditor extends Component { Button.component({ icon: 'upload', children: app.translator.trans('core.forum.user.avatar_upload_button'), - onclick: this.upload.bind(this) + onclick: this.openPicker.bind(this) }) ); @@ -77,6 +89,40 @@ export default class AvatarEditor extends Component { return items; } + /** + * Enable dragover style + * + * @param {Event} e + */ + enableDragover(e) { + e.preventDefault(); + e.stopPropagation(); + this.isDraggedOver = true; + } + + /** + * Disable dragover style + * + * @param {Event} e + */ + disableDragover(e) { + e.preventDefault(); + e.stopPropagation(); + this.isDraggedOver = false; + } + + /** + * Upload avatar when file is dropped into dropzone. + * + * @param {Event} e + */ + dropUpload(e) { + e.preventDefault(); + e.stopPropagation(); + this.isDraggedOver = false; + this.upload(e.dataTransfer.files[0]); + } + /** * If the user doesn't have an avatar, there's no point in showing the * controls dropdown, because only one option would be viable: uploading. @@ -89,14 +135,14 @@ export default class AvatarEditor extends Component { if (!this.props.user.avatarUrl()) { e.preventDefault(); e.stopPropagation(); - this.upload(); + this.openPicker(); } } /** - * Prompt the user to upload a new avatar. + * Upload avatar using file picker */ - upload() { + openPicker() { if (this.loading) return; // Create a hidden HTML input element and click on it so the user can select @@ -105,24 +151,36 @@ export default class AvatarEditor extends Component { const $input = $(''); $input.appendTo('body').hide().click().on('change', e => { - const data = new FormData(); - data.append('avatar', $(e.target)[0].files[0]); - - this.loading = true; - m.redraw(); - - app.request({ - method: 'POST', - url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar', - serialize: raw => raw, - data - }).then( - this.success.bind(this), - this.failure.bind(this) - ); + this.upload($(e.target)[0].files[0]); }); } + /** + * Upload avatar + * + * @param {File} file + */ + upload(file) { + if (this.loading) return; + + const user = this.props.user; + const data = new FormData(); + data.append('avatar', file); + + this.loading = true; + m.redraw(); + + app.request({ + method: 'POST', + url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar', + serialize: raw => raw, + data + }).then( + this.success.bind(this), + this.failure.bind(this) + ); + } + /** * Remove the user's avatar. */ diff --git a/less/forum/AvatarEditor.less b/less/forum/AvatarEditor.less index 72fc55eda..7f6dc7a9b 100644 --- a/less/forum/AvatarEditor.less +++ b/less/forum/AvatarEditor.less @@ -17,7 +17,10 @@ .AvatarEditor--noAvatar { opacity: 0.7; } - &:hover .Dropdown-toggle, &.open .Dropdown-toggle, &.loading .Dropdown-toggle { + &:hover .Dropdown-toggle, + &.open .Dropdown-toggle, + &.loading .Dropdown-toggle, + &.dragover .Dropdown-toggle { opacity: 1; } .LoadingIndicator { From 2754a8c8671a404a3d69bc80cf45d7e99559052c Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Wed, 25 Oct 2017 12:36:09 +1030 Subject: [PATCH 27/72] Add LESS variable to configure expansion of sideNav dropdowns --- less/lib/sideNav.less | 71 ++++++++++++++++++++++------------------- less/lib/variables.less | 2 ++ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/less/lib/sideNav.less b/less/lib/sideNav.less index ebc1caee2..13383a41b 100755 --- a/less/lib/sideNav.less +++ b/less/lib/sideNav.less @@ -9,7 +9,7 @@ // control; the new discussion button is the primary-control. On anything // larger than a phone, however, we need to affix the sidebar and expand the // .dropdown-select into a plain list. -@media @tablet-up { +@media @expand-side-nav { .sideNav { // Expand the dropdown-select component into a normal nav list. & .Dropdown--select { @@ -48,45 +48,50 @@ } } -.sideNav--horizontal { - padding: 15px 0; - white-space: nowrap; - overflow: auto; - -webkit-overflow-scrolling: touch; +.sideNav--horizontal {} - &:after { - content: " "; - position: absolute; - left: 0; - right: 0; - margin-top: 15px; - border-bottom: 1px solid @control-bg; - } +@media @expand-side-nav { + .sideNav--horizontal { + padding: 15px 0; + white-space: nowrap; + overflow: auto; + -webkit-overflow-scrolling: touch; - > ul > li, .Dropdown-menu > li { - display: inline-block; - margin: 0 20px 0 0; - vertical-align: top; - } - .Dropdown-separator { - display: none; - } + &:after { + content: " "; + position: absolute; + left: 0; + right: 0; + margin-top: 15px; + border-bottom: 1px solid @control-bg; + } - .Dropdown--select .Dropdown-menu > li > a { - padding-left: 25px; + > ul > li, .Dropdown-menu > li { + display: inline-block; + margin: 0 20px 0 0; + vertical-align: top; + } + .Dropdown-separator { + display: none; + } - .icon { - margin-left: -25px; + .Dropdown--select .Dropdown-menu > li > a { + padding-left: 25px; + + .icon { + margin-left: -25px; + } + } + + .affix { + position: static; } } - .affix { - position: static; - } -} -@media @tablet { - .sideNav { - .sideNav--horizontal(); + @media @tablet { + .sideNav { + .sideNav--horizontal(); + } } } diff --git a/less/lib/variables.less b/less/lib/variables.less index 501e52372..bc2ba5f32 100755 --- a/less/lib/variables.less +++ b/less/lib/variables.less @@ -110,6 +110,8 @@ @zindex-alerts: 1060; @zindex-tooltip: 1070; +@expand-side-nav: @tablet-up; + // --------------------------------- // BREAKPOINTS From 40ebc13292b64271418aa3d5683d0927e6db778e Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Wed, 25 Oct 2017 13:40:57 +1030 Subject: [PATCH 28/72] Only apply custom CSS and header HTML on forum, not admin --- src/Forum/WebApp.php | 4 ++++ src/Http/WebApp/AbstractWebApp.php | 4 ---- views/admin.blade.php | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Forum/WebApp.php b/src/Forum/WebApp.php index 310532922..eecfbee44 100644 --- a/src/Forum/WebApp.php +++ b/src/Forum/WebApp.php @@ -51,6 +51,10 @@ class WebApp extends AbstractWebApp return $this->formatter->getJs(); }); + $view->getCss()->addString(function () { + return $this->settings->get('custom_less'); + }); + return $view; } diff --git a/src/Http/WebApp/AbstractWebApp.php b/src/Http/WebApp/AbstractWebApp.php index 0e2bc24aa..e20ec3cac 100644 --- a/src/Http/WebApp/AbstractWebApp.php +++ b/src/Http/WebApp/AbstractWebApp.php @@ -131,10 +131,6 @@ abstract class AbstractWebApp $css->addString($lessVariables); $localeCss->addString($lessVariables); - - $css->addString(function () { - return $this->settings->get('custom_less'); - }); } /** diff --git a/views/admin.blade.php b/views/admin.blade.php index 80a0856a8..47275da32 100644 --- a/views/admin.blade.php +++ b/views/admin.blade.php @@ -1,5 +1,3 @@ -{!! array_get($forum, 'attributes.headerHtml') !!} -
    From eeed7c20e142ee74236de207c40270c7a52a9581 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 30 Oct 2017 02:54:02 +1030 Subject: [PATCH 29/72] Fix blurry chrome image rendering for Avatar / Logo - closes #1259 (#1276) * Fix blurry chrome image rendering for Avatar / Logo - closes #1259 * Add comments for Chrome css fix --- less/lib/App.less | 2 ++ less/lib/Avatar.less | 2 ++ 2 files changed, 4 insertions(+) diff --git a/less/lib/App.less b/less/lib/App.less index c8af74833..0ee490ba5 100755 --- a/less/lib/App.less +++ b/less/lib/App.less @@ -171,6 +171,8 @@ .Header-logo { max-height: 30px; vertical-align: middle; + // Prevent blurriness in Chrome + image-rendering: -webkit-optimize-contrast; } // On phones, the header is displayed inside of the drawer. We lay its diff --git a/less/lib/Avatar.less b/less/lib/Avatar.less index 5fe5054c8..b65e014c8 100755 --- a/less/lib/Avatar.less +++ b/less/lib/Avatar.less @@ -14,6 +14,8 @@ height: 100%; border-radius: 100%; vertical-align: top; + // Prevent blurriness in Chrome + image-rendering: -webkit-optimize-contrast; } } From 1b7cb3bec2e5f413e9c22d20d5a848c5c640cbe1 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 2 Nov 2017 00:51:31 +0100 Subject: [PATCH 30/72] The CookieFactory now also works if no configuration exists (#1258) * Returning the $default value if there's no config This is especially important for the CookieFactory which accesses the configuration before the application is installed * Injecting the configuration values into the CookieFactory --- src/Foundation/Application.php | 2 +- src/Http/CookieFactory.php | 52 +++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index a432bdbb8..b3edee9fe 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -143,7 +143,7 @@ class Application extends Container implements ApplicationContract */ public function config($key, $default = null) { - return array_get($this->make('flarum.config'), $key, $default); + return $this->isInstalled() ? array_get($this->make('flarum.config'), $key, $default) : $default; } /** diff --git a/src/Http/CookieFactory.php b/src/Http/CookieFactory.php index aacaff7f5..0ddb3de4c 100644 --- a/src/Http/CookieFactory.php +++ b/src/Http/CookieFactory.php @@ -17,16 +17,46 @@ use Flarum\Foundation\Application; class CookieFactory { /** - * @var Application + * The prefix for the cookie names. + * + * @var string */ - protected $app; + protected $prefix; + + /** + * A path scope for the cookies. + * + * @var string + */ + protected $path; + + /** + * A domain scope for the cookies. + * + * @var string + */ + protected $domain; + + /** + * Whether the cookie(s) can be requested only over HTTPS. + * + * @var bool + */ + protected $secure; /** * @param Application $app */ public function __construct(Application $app) { - $this->app = $app; + // Parse the forum's base URL so that we can determine the optimal cookie settings + $url = parse_url(rtrim($app->url(), '/')); + + // Get the cookie settings from the config or use the default values + $this->prefix = $app->config('cookie.name', 'flarum'); + $this->path = $app->config('cookie.path', array_get($url, 'path') ?: '/'); + $this->domain = $app->config('cookie.domain'); + $this->secure = $app->config('cookie.secure', array_get($url, 'scheme') === 'https'); } /** @@ -42,9 +72,6 @@ class CookieFactory */ public function make($name, $value = null, $maxAge = null) { - // Parse the forum's base URL so that we can determine the optimal cookie settings - $url = parse_url(rtrim($this->app->url(), '/')); - $cookie = SetCookie::create($this->getName($name), $value); // Make sure we send both the MaxAge and Expires parameters (the former @@ -55,16 +82,13 @@ class CookieFactory ->withExpires(time() + $maxAge); } - if ($domain = $this->app->config('cookie.domain')) { - $cookie = $cookie->withDomain($domain); + if ($this->domain != null) { + $cookie = $cookie->withDomain($this->domain); } - $path = $this->app->config('cookie.path', array_get($url, 'path') ?: '/'); - $secure = $this->app->config('cookie.secure', array_get($url, 'scheme') === 'https'); - return $cookie - ->withPath($path) - ->withSecure($secure) + ->withPath($this->path) + ->withSecure($this->secure) ->withHttpOnly(true); } @@ -87,6 +111,6 @@ class CookieFactory */ public function getName($name) { - return $this->app->config('cookie.name', 'flarum').'_'.$name; + return $this->prefix.'_'.$name; } } From 914b94b62d7316498b45d3a684a04ad502e2d053 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Thu, 2 Nov 2017 01:12:49 +0100 Subject: [PATCH 31/72] Remove user bio feature (#1214) The feature is very limited in scope, and we hope for community extensions to take over this feature and make it much better. --- js/admin/dist/app.js | 4 - js/forum/dist/app.js | 148 +------------------------- js/forum/src/components/UserBio.js | 104 ------------------ js/forum/src/components/UserCard.js | 8 -- js/lib/models/User.js | 2 - less/forum/UserCard.less | 31 ------ src/Api/Serializer/UserSerializer.php | 1 - src/Core/Command/EditUserHandler.php | 8 -- src/Core/User.php | 17 --- src/Event/UserBioWasChanged.php | 37 ------- 10 files changed, 2 insertions(+), 358 deletions(-) delete mode 100644 js/forum/src/components/UserBio.js delete mode 100644 src/Event/UserBioWasChanged.php diff --git a/js/admin/dist/app.js b/js/admin/dist/app.js index b8f9c3745..4a38b31f5 100644 --- a/js/admin/dist/app.js +++ b/js/admin/dist/app.js @@ -22451,10 +22451,6 @@ System.register('flarum/models/User', ['flarum/Model', 'flarum/utils/stringToCol password: Model.attribute('password'), avatarUrl: Model.attribute('avatarUrl'), - bio: Model.attribute('bio'), - bioHtml: computed('bio', function (bio) { - return bio ? '

    ' + $('

    ').text(bio).html().replace(/\n/g, '
    ').autoLink({ rel: 'nofollow' }) + '

    ' : ''; - }), preferences: Model.attribute('preferences'), groups: Model.hasMany('groups'), diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js index 8a51d8577..efc6992d7 100644 --- a/js/forum/dist/app.js +++ b/js/forum/dist/app.js @@ -28478,143 +28478,10 @@ System.register('flarum/components/TextEditor', ['flarum/Component', 'flarum/uti });; 'use strict'; -System.register('flarum/components/UserBio', ['flarum/Component', 'flarum/components/LoadingIndicator', 'flarum/utils/classList', 'flarum/utils/extractText'], function (_export, _context) { +System.register('flarum/components/UserCard', ['flarum/Component', 'flarum/utils/humanTime', 'flarum/utils/ItemList', 'flarum/utils/UserControls', 'flarum/helpers/avatar', 'flarum/helpers/username', 'flarum/helpers/icon', 'flarum/components/Dropdown', 'flarum/components/AvatarEditor', 'flarum/helpers/listItems'], function (_export, _context) { "use strict"; - var Component, LoadingIndicator, classList, extractText, UserBio; - return { - setters: [function (_flarumComponent) { - Component = _flarumComponent.default; - }, function (_flarumComponentsLoadingIndicator) { - LoadingIndicator = _flarumComponentsLoadingIndicator.default; - }, function (_flarumUtilsClassList) { - classList = _flarumUtilsClassList.default; - }, function (_flarumUtilsExtractText) { - extractText = _flarumUtilsExtractText.default; - }], - execute: function () { - UserBio = function (_Component) { - babelHelpers.inherits(UserBio, _Component); - - function UserBio() { - babelHelpers.classCallCheck(this, UserBio); - return babelHelpers.possibleConstructorReturn(this, (UserBio.__proto__ || Object.getPrototypeOf(UserBio)).apply(this, arguments)); - } - - babelHelpers.createClass(UserBio, [{ - key: 'init', - value: function init() { - /** - * Whether or not the bio is currently being edited. - * - * @type {Boolean} - */ - this.editing = false; - - /** - * Whether or not the bio is currently being saved. - * - * @type {Boolean} - */ - this.loading = false; - } - }, { - key: 'view', - value: function view() { - var user = this.props.user; - var content = void 0; - - if (this.editing) { - content = m('textarea', { className: 'FormControl', placeholder: extractText(app.translator.trans('core.forum.user.bio_placeholder')), rows: '3', value: user.bio() }); - } else { - var subContent = void 0; - - if (this.loading) { - subContent = m( - 'p', - { className: 'UserBio-placeholder' }, - LoadingIndicator.component({ size: 'tiny' }) - ); - } else { - var bioHtml = user.bioHtml(); - - if (bioHtml) { - subContent = m.trust(bioHtml); - } else if (this.props.editable) { - subContent = m( - 'p', - { className: 'UserBio-placeholder' }, - app.translator.trans('core.forum.user.bio_placeholder') - ); - } - } - - content = m( - 'div', - { className: 'UserBio-content', onclick: this.edit.bind(this) }, - subContent - ); - } - - return m( - 'div', - { className: 'UserBio ' + classList({ - editable: this.props.editable, - editing: this.editing - }) }, - content - ); - } - }, { - key: 'edit', - value: function edit() { - if (!this.props.editable) return; - - this.editing = true; - m.redraw(); - - var bio = this; - var save = function save(e) { - if (e.shiftKey) return; - e.preventDefault(); - bio.save($(this).val()); - }; - - this.$('textarea').focus().bind('blur', save).bind('keydown', 'return', save); - } - }, { - key: 'save', - value: function save(value) { - var _this2 = this; - - var user = this.props.user; - - if (user.bio() !== value) { - this.loading = true; - - user.save({ bio: value }).catch(function () {}).then(function () { - _this2.loading = false; - m.redraw(); - }); - } - - this.editing = false; - m.redraw(); - } - }]); - return UserBio; - }(Component); - - _export('default', UserBio); - } - }; -});; -'use strict'; - -System.register('flarum/components/UserCard', ['flarum/Component', 'flarum/utils/humanTime', 'flarum/utils/ItemList', 'flarum/utils/UserControls', 'flarum/helpers/avatar', 'flarum/helpers/username', 'flarum/helpers/icon', 'flarum/components/Dropdown', 'flarum/components/UserBio', 'flarum/components/AvatarEditor', 'flarum/helpers/listItems'], function (_export, _context) { - "use strict"; - - var Component, humanTime, ItemList, UserControls, avatar, username, icon, Dropdown, UserBio, AvatarEditor, listItems, UserCard; + var Component, humanTime, ItemList, UserControls, avatar, username, icon, Dropdown, AvatarEditor, listItems, UserCard; return { setters: [function (_flarumComponent) { Component = _flarumComponent.default; @@ -28632,8 +28499,6 @@ System.register('flarum/components/UserCard', ['flarum/Component', 'flarum/utils icon = _flarumHelpersIcon.default; }, function (_flarumComponentsDropdown) { Dropdown = _flarumComponentsDropdown.default; - }, function (_flarumComponentsUserBio) { - UserBio = _flarumComponentsUserBio.default; }, function (_flarumComponentsAvatarEditor) { AvatarEditor = _flarumComponentsAvatarEditor.default; }, function (_flarumHelpersListItems) { @@ -28713,11 +28578,6 @@ System.register('flarum/components/UserCard', ['flarum/Component', 'flarum/utils var user = this.props.user; var lastSeenTime = user.lastSeenTime(); - items.add('bio', UserBio.component({ - user: user, - editable: this.props.editable - })); - if (lastSeenTime) { var online = user.isOnline(); @@ -30541,10 +30401,6 @@ System.register('flarum/models/User', ['flarum/Model', 'flarum/utils/stringToCol password: Model.attribute('password'), avatarUrl: Model.attribute('avatarUrl'), - bio: Model.attribute('bio'), - bioHtml: computed('bio', function (bio) { - return bio ? '

    ' + $('

    ').text(bio).html().replace(/\n/g, '
    ').autoLink({ rel: 'nofollow' }) + '

    ' : ''; - }), preferences: Model.attribute('preferences'), groups: Model.hasMany('groups'), diff --git a/js/forum/src/components/UserBio.js b/js/forum/src/components/UserBio.js deleted file mode 100644 index 41795a05f..000000000 --- a/js/forum/src/components/UserBio.js +++ /dev/null @@ -1,104 +0,0 @@ -import Component from 'flarum/Component'; -import LoadingIndicator from 'flarum/components/LoadingIndicator'; -import classList from 'flarum/utils/classList'; -import extractText from 'flarum/utils/extractText'; - -/** - * The `UserBio` component displays a user's bio, optionally letting the user - * edit it. - */ -export default class UserBio extends Component { - init() { - /** - * Whether or not the bio is currently being edited. - * - * @type {Boolean} - */ - this.editing = false; - - /** - * Whether or not the bio is currently being saved. - * - * @type {Boolean} - */ - this.loading = false; - } - - view() { - const user = this.props.user; - let content; - - if (this.editing) { - content =