Updating For 1.0
Flarum version 1.0 is the long-awaited stable release! This release brings a number of refactors, cleanup, and small improvements that should make your Flarum experience just a bit better!
If you need help applying these changes or using new features, please start a discussion on the community forum or Discord chat.
Full Stack
Translations and transChoice
Background
Historically, Flarum has used Symfony for backend translations, and a port for frontend translations to keep format consistent. There are a few limitations to this approach though:
- Developers need to decide between using
trans
ortransChoice
for pluralization - The pluralization format is proprietary to Symfony
- We have to maintain the JS port ourselves
- Keys for values provided to backend translations need to be wrapped in curly braces. (e.g.
$this->translator->trans('some.translation', ['{username}' => 'Some Username!'])
). - There's no support for complex applications (nested pluralization, non-number-based selection)
- As a result of the previous point, genderization is impossible. And that's kinda important for a lot of languages.
New System
In v5, Symfony dropped their proprietary transChoice
system in favor of the more-or-less standard ICU MessageFormat. This solves pretty much every single one of the aforementioned issues. In this release, Flarum will be fully switching to ICU MessageFormat as well. What does this mean for extensions?
transChoice
should not be used at all; instead, the variable passed for pluralization should be included in the data.- Keys for backend translations no longer need to be surrounded by curly braces.
- Translations can now use the
select
andplural
formatter syntaxes. For theplural
formatter, theoffset
parameter and#
magic variables are supported. - These
select
andplural
syntaxes can be nested to arbitrary depth. This is often a bad idea though (beyond, say, 2 levels), as things can get unnecessarily complex.
No change to translation file naming is necessary (Symfony docs say that an +intl-icu
suffix is necessary, but Flarum will now interpret all translation files as internationalized).
Future Changes
In the future, this will serve as a basis for additional features:
- Translator preprocessors will allow extensions to modify arguments passed to translations. This will enable genderization (extensions could automatically extract a gender field from any objects of type "user" passed in).
- We could support internationalized "magic variables" for numbers: currently,
one
is supported, but others (few
,many
, etc) currently aren't. - We could support ordinal formatting in additional to just plural formatting.
Changes Needed in Extensions
The transChoice
methods in the frontend and backend have been removed. The trans
method should always be used for translating, regardless of pluralization. If a translation requires pluralization, make sure you pass in the controlling variable as one of the arguments.
In the frontend, code that looked like this:
app.translator.transChoice('some-translation', guestCount, {host: hostName});
should be changed to:
// This uses ES6 key-property shorthand notation. {guestCount: guestCount} is equivalent to {guestCount}
app.translator.trans('some-translation', {host: hostName, guestCount });
Similarly, in the backend,
$translator->transChoice('some-translation', $guestCount, ['{host}' => $hostName]);
should be changed to:
$translator->trans('some-translation', ['host' => $hostName, 'guestCount' => $guestCount]);
Note that in the backend, translation keys were previously wrapped in curly braces. This is no longer needed.
Changes Needed in Translations
Translations that aren't using pluralization don't need any changes.
Pluralized translations should be changed as follows:
For {count} minute|For {count} minutes
to
{count, plural, one {For # minute} other {For # minutes}}
Note that in this example, count
is the variable that controls pluralization. If a different variable were used (such as guestCount in the example above), this would look like:
{guestCount, plural, one {For # minute} other {For # minutes}}
See our i18n docs for more information.
Permissions Changes
For a long time, the viewDiscussions
and viewUserList
permissions have been confusing. Despite their names:
viewDiscussions
controls viewing both discussions and users.viewUserList
controls searching users, not viewing user profiles.
To clear this up, in v1.0, these permissions have been renamed to viewForum
and searchUsers
respectively. A migration in core will automatically adjust all permissions in the database to use the new naming. However, any extension code using the old name must switch to the new ones immediately to avoid security issues. To help the transfer, a warning will be thrown if the old permissions are referenced.
We have also slightly improved tag scoping for permissions. Currently, permissions can be applied to tags if:
- The permission is
viewForum
- The permission is
startDiscussion
- The permission starts with
discussion.
However, this doesn't work for namespaced permissions (flarum-acme.discussion.bookmark
), or permissions that don't really have anything to do with discussions, but should still be scoped (e.g. viewTag
). To counter this, a tagScoped
attribute can be used on the object passed to registerPermission
to explicitly indicate whether the permission should be tag scopable. If this attribute is not provided, the current rules will be used to determine whether the permission should be tag scopable.
Frontend
Tooltip Changes
The flarum/common/components/Tooltip
component has been introduced as a simpler and less framework-dependent way to add tooltips. If your code is creating tooltips directly (e.g. via $.tooltip()
in oncreate
methods), you should wrap your components in the Tooltip
component instead. For example:
<Tooltip text="You wish!">
<button>
Click for free money!
</button>
</Tooltip>
See the source code for more examples and instructions.
See the PR for examples of how to change existing code to use tooltips.
PaginatedListState
The flarum/common/states/PaginatedListState
state class has been introduced to abstract away most of the logic of DiscussionListState
and NotificationListState
. It provides support for loading and displaying paginated lists of JSON:API resources (usually models). In future releases, we will also provide an PaginatedList
(or InfiniteScroll
) component that can be used as a base class for these paginated lists.
Please see the source code for a list of methods.
Note that flarum/forum/states/DiscussionListState
's empty
and hasDiscussions
methods have been removed, and replaced with isEmpty
and hasItems
respectively. This is a breaking change.
New Loading Spinner
The old spin.js
based loading indicator has been replaced with a CSS-based solution. For the most part, no changes should be needed in extensions, but in some cases, you might need to update your spinner. This change also makes it easier to customize the spinner.
See this discussion for more information.
classList util
Ever wrote messy code trying to put together a list of classes for some component? Well, no longer! The clsx library is now available as the flarum/common/utils/classList
util.
User List
An extensible user list has been added to the admin dashboard. In future releases, we hope to extract a generic model table component that can be used to list any model in the admin dashboard.
See the source code for a list of methods to extend, and examples of how columns should look like (can be added by extending the columns
method and adding items to the ItemList).
Miscellaneous
- Components should now call
super
for ALL Mithril lifecycle methods they define. Before, this was only needed foroninit
,onbeforeupdate
, andoncreate
. Now, it is also needed inonupdate
,onbeforeremove
, andonremove
. See this GitHub issue for information on why this change was made. - The
flarum/common/utils/insertText
andflarum/common/utils/styleSelectedText
utils have been moved to core fromflarum/markdown
. Seeflarum/markdown
for an example of usage. - The
extend
andoverride
utils can now modify several methods at once by passing in an array of method names instead of a single method name string as the second argument. This is useful for extending theoncreate
andonupdate
methods at once. - The
EditUserModal
component is no longer available through theflarum/forum
namespace, it has been moved toflarum/common
. Imports should be adjusted. - The
Model
andRoute
JS extenders have been removed for now. There aren't currently used in any extensions that we know of. We will be reintroducing JS extenders during v1.x releases. - The
Search
component can now be used with theSearchState
state class. Previously,SearchState
was missing thegetInitialSearch
method expected by theSearch
component.
Backend
Filesystem Extenders
In this release, we refactored our use of the filesystem to more consistently use Laravel's filesystem API. Extensions can now declare new disks, more easily use core's flarum-assets
and flarum-avatars
disks, and create their own storage drivers, enabling CDN and cloud storage support.
Compat and Closure Extenders
In early Flarum versions, the extend.php
file allowed arbitrary functions that allowed execution of arbitrary code one extension boot. For example:
return [
// other extenders
function (Dispatcher $events) {
$events->subscribe(Listener\FilterDiscussionListByTags::class);
$events->subscribe(Listener\FilterPostsQueryByTag::class);
$events->subscribe(Listener\UpdateTagMetadata::class);
}
];
This approach was difficult to maintain and provide a well-tested public API for, frequently resolved classes early (breaking all sorts of things), and was not very descriptive. With the extender API completed in beta 16, this approach is no longer necessary. Support for these closures has been removed in this stable version.
One type of functionality for which the extender replacement isn't obvious is container bindings (e.g. flarum/pusher). This can be done with via the service provider extender (e.g. a newer version of flarum/pusher).
If you are unsure about which extenders should be used to replace your use of callbacks in extend.php
, or are not sure that such an extender exists, please comment so below or reach out! We're in the final stages of finishing up the extender API, so now is the time to comment.
Scheduled Commands
The fof/console library has been a popular way to schedule commands (e.g. for publishing scheduled posts, running DB-heavy operations, etc) for several release. In Flarum 1.0, this functionality has been brought into core's Console
extender. See our console extension documentation for more information on how to create schedule commands, and our console user documentation for more information on how to run scheduled commands.
Eager Loading Extender
As part of solving N+1 Query issues in some Flarum API endpoints, we have introduced a load
method on the ApiController
extender that allows you to indicate relations that should be eager loaded.
This should be done if you know a relation will always be included, or will always be referenced by controller / permission logic. For example, we will always need the tags of a discussion to check what permissions a user has on that discussion, so we should eager load the discussion's tags
relationship. For example:
return [
// other extenders
(new Extend\ApiController(FlarumController\ListDiscussionsController::class))
->addInclude(['tags', 'tags.state', 'tags.parent'])
->load('tags'),
];
RequestUtil
The Flarum\Http\RequestUtil
's getActor
and withActor
should be used for getting/setting the actor (user) on requests. $request->getAttribute('actor')
and $request->withAttribute('actor')
are deprecated, and will be removed in v2.0.
Miscellaneous
- The
Formatter
extender now has anunparse
method that allows modifying XML before unparsing content. - All route names must now be unique. In beta 16, uniqueness was enforced per-method; now, it is mandatory for all routes.
- API requests sent through
Flarum\Api\Client
now run through middleware, includingThrottleApi
. This means that it is now possible to throttle login/registration calls. - In beta 16, registering custom searchers was broken. It has been fixed in stable.
- The
post_likes
table in the flarum/likes extension now logs the timestamp when likes were created. This isn't used in the extension, but could be used in other extensions for analytics. - The
help
attribute of admin settings no longer disappears on click. - The
generate:migration
console command has been removed. The Flarum CLI should be used instead. - The
GambitManager
util class is now considered internal API, and should not be used directly by extensions. - The
session
attribute is no longer available on theUser
class. This caused issues with queue drivers, and was not conceptually correct (a user can have multiple sessions). The current session is still available via the$request
instance. - The
app
,base_path
,public_path
,storage_path
, andevent
global helpers have been restored, but deprecated perpetually. These exist in case Laravel packages need them; they should not be used directly by Flarum extension code. Theflarum/laravel-helpers
package has been abandoned. - The following deprecated features from beta 16 have been removed:
- The
TextEditor
,TextEditorButton
, andSuperTextarea
components are no longer exported through theflarum/forum
namespace, but throughflarum/common
(with the exception ofSuperTextarea
, which has been replaced withflarum/common/utils/BasicEditorDriver
). - Support for
Symfony\Component\Translation\TranslatorInterface
has been removed,Symfony\Contracts\Translation\TranslatorInterface
should be used instead. - All backwards compatibility layers for the beta 16 access token refactors have been removed
- The
GetModelIsPrivate
event has been removed. TheModelPrivate
extender should be used instead. - The
Searching
(for bothUser
andDiscussion
models),ConfigureAbstractGambits
,ConfigureDiscussionGambits
, andConfigureUserGambits
events have been removed. TheSimpleFlarumSearch
extender should be used instead. - The
ConfigurePostsQuery
event has been removed. TheFilter
extender should be used instead. - The
ApiSerializer
extender'smutate
method has been removed. The same class'sattributes
method should be used instead. - The
SearchCriteria
andSearchResults
util classes have been removed.Flarum\Query\QueryCriteria
andFlarum\Query\QueryResults
should be used instead. - The
pattern
property ofAbstractRegexGambit
has been removed; thegetGambitPattern
method is now a required abstract method. - The
AbstractSearch
util class has been removed.Flarum\Search\SearchState
andFlarum\Filter\FilterState
should be used instead. - The
CheckingPassword
event has been removed, theAuth
extender should be used instead.
- The
Tags Extension Changes
As mentioned above, tag scopable permissions can now be explicitly declared.
We've also made several big refactors to significantly improve tags performance.
Firstly, the Tag model's static getIdsWhereCan
and getIdsWhereCannot
methods have been removed. These methods scaled horribly on forums with many tags, sometimes adding several seconds to response time.
These methods have been replaced with a whereHasPermission
Eloquent dynamic scope that returns a query. For example:
Tag::whereHasPermission($actor, 'viewDiscussions)
.
That query can then be used in other DB queries, or further constricted to retrieve individual tags.
Secondly, we no longer load in all tags as part of the initial payload. The initial payload contains all top-level primary tags, and the top 3 secondary tags. Other tags are loaded in as needed. Future releases will paginate secondary tags. This should enable forums to have thousands of secondary tags without significant performance impacts. These changes mean that extensions should not assume that all tags are available in the model store.
Testing Library Changes
- Bundled extensions will not be automatically enabled; all enabled extensions must be specified in that test case.
setting
andconfig
methods have been added that allow configuring settings and config.php values before the tested application boots. See the testing docs for more information.- The
php flarum test:setup
command will now drop the existing test DB tables before creating the database. This means that you can runphp flarum test:setup
to ensure a clean database without needing to go into the database and drop tables manually.