diff --git a/docs/README.md b/docs/README.md index 45d90f93e..c4acd4179 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ slug: / Flarum is a delightfully simple discussion platform for your website. It's fast, free, and easy to use, with all the features you need to run a successful community. It's also extremely extensible, allowing for ultimate customizability. -![Flarum Home Screenshot](/en/img/home_screenshot.png) +![Flarum Home Screenshot](./assets/home_screenshot.png) ## Goals @@ -16,7 +16,7 @@ Flarum is the combined successor of [esoTalk](https://github.com/esotalk/esoTalk * **Beautiful and responsive.** This is forum software for humans. Flarum is carefully designed to be consistent and intuitive across platforms, out-of-the-box. -* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarum’s architecture is amazingly flexible, with a [powerful Extension API](/extend/README.md). +* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarum’s architecture is amazingly flexible, with a [powerful Extension API](./extend/README.md). * **Free and open.** Flarum is released under the [MIT license](https://github.com/flarum/flarum/blob/master/LICENSE). diff --git a/docs/admin.md b/docs/admin.md index c412a14b7..4956589bc 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -11,5 +11,6 @@ The Admin Dashboard has the following sections, being: - **Permissions** - Shows the permissions for each user group, and allows you to configure global and specific scopes. - **Appearance** - Allows you to customize the forum's colors, branding and add additional CSS for customization. - **Users** - Provides you with a paginated list of all the users in the forum, and grants you the ability to edit the user or take administrative actions. +- **Advanced** - Allows you to configure advanced settings such as Maintenance Mode, Search drivers, and more. -Apart from the above mentioned sections, the Admin Dashboard also allows you to manage your Extensions (including the flarum core extensions such as Tags) under the _Features_ section. Extensions which modify the forum theme, or allow you to use multiple languages are categorized under the _Themes_ and _Languages_ section respectively. +Apart from the above-mentioned sections, the Admin Dashboard also allows you to manage your Extensions (including the flarum core extensions such as Tags) under the _Features_ section. Extensions which modify the forum theme, or allow you to use multiple languages are categorized under the _Themes_ and _Languages_ section respectively. diff --git a/docs/assets/api_flowchart.svg b/docs/assets/api_flowchart.svg new file mode 100644 index 000000000..04921c3ad --- /dev/null +++ b/docs/assets/api_flowchart.svg @@ -0,0 +1,31 @@ +API ResourceAPI ResourceEndpointEndpointDatabaseDatabaseFieldsFieldsFieldsFieldsFieldsFields2. Pass request through middleware  +to matching endpoint2. Pass request through middleware  +to matching endpoint1. Send request in JSON:API  +format1. Send request in JSON:API  +format7 (cont). Return JSON response7 (cont). Return JSON response8 (optional). Convert JSON response to frontend  +models, add to `app.store`.8 (optional). Convert JSON response to frontend  +models, add to `app.store`.7. Pass through middleware again,  +return JSON response7. Pass through middleware again,  +return JSON response3. Apply extension  +changes to endpoint3. Apply extension  +changes to endpoint5. Apply extension changes  +to serialization fields5. Apply extension changes  +to serialization fields6. Serialize  +data6. Serialize  +data4. Run endpoint logic,  +interact with database4. Run endpoint logic,  +interact with databaseapp.storeapp.store \ No newline at end of file diff --git a/docs/assets/config-repositories.png b/docs/assets/config-repositories.png new file mode 100644 index 000000000..24f6ffb68 Binary files /dev/null and b/docs/assets/config-repositories.png differ diff --git a/docs/assets/extension-manager-page.png b/docs/assets/extension-manager-page.png new file mode 100644 index 000000000..8a73ee160 Binary files /dev/null and b/docs/assets/extension-manager-page.png differ diff --git a/docs/assets/extension-manager-queue.png b/docs/assets/extension-manager-queue.png new file mode 100644 index 000000000..523665afb Binary files /dev/null and b/docs/assets/extension-manager-queue.png differ diff --git a/docs/assets/gambit_autocomplete_dropdown.png b/docs/assets/gambit_autocomplete_dropdown.png new file mode 100644 index 000000000..be97d30b8 Binary files /dev/null and b/docs/assets/gambit_autocomplete_dropdown.png differ diff --git a/docs/assets/global_search_modal.png b/docs/assets/global_search_modal.png new file mode 100644 index 000000000..f700d8405 Binary files /dev/null and b/docs/assets/global_search_modal.png differ diff --git a/docs/assets/home_screenshot.png b/docs/assets/home_screenshot.png new file mode 100644 index 000000000..1bb9cb2aa Binary files /dev/null and b/docs/assets/home_screenshot.png differ diff --git a/docs/assets/install-extension.png b/docs/assets/install-extension.png new file mode 100644 index 000000000..08000b9ed Binary files /dev/null and b/docs/assets/install-extension.png differ diff --git a/docs/assets/uninstall-extension.png b/docs/assets/uninstall-extension.png new file mode 100644 index 000000000..4a4e1404d Binary files /dev/null and b/docs/assets/uninstall-extension.png differ diff --git a/docs/assets/update-extension.png b/docs/assets/update-extension.png new file mode 100644 index 000000000..bc0586e7b Binary files /dev/null and b/docs/assets/update-extension.png differ diff --git a/docs/bugs.md b/docs/bugs.md index 85e8ea193..1a486d832 100644 --- a/docs/bugs.md +++ b/docs/bugs.md @@ -13,7 +13,7 @@ Thank you for helping us test Flarum. We're happy to have you on the team! We ne Found a bug already? Wonderful! We'd love to hear about it — but first you should check around to make sure you're not wasting your time on a known issue: - Search our [Support forum](https://discuss.flarum.org/t/support) to see if it's already been reported. -- We could be working on a fix, so search our [issue tracker](https://github.com/flarum/core/issues) too. +- We could be working on a fix, so search our [issue tracker](https://github.com/flarum/framework/issues) too. If you've searched *thoroughly* and come up empty-handed, we'll welcome your report. If it's just a simple issue (a misspelled word or graphics glitch, for example) skip to the next paragraph. But if you're seeing errors, or something is clearly broken, we'll need you to gather some information first. Please head over to our [Troubleshooting](troubleshoot.md) guide and follow the instructions there. Collect as much info as you can! diff --git a/docs/config.md b/docs/config.md index 2b23101e9..fdfa195cf 100644 --- a/docs/config.md +++ b/docs/config.md @@ -12,10 +12,10 @@ Here's a quick overview of what everything means with an example file: ```php false, // enables or disables debug mode, used to troubleshoot issues - 'offline' => false, // enables or disables site maintenance mode. This makes your site inaccessible to all users (including admins). + 'offline' => false, // none, high, low or safe. 'database' => array ( - 'driver' => 'mysql', // the database driver, i.e. MySQL, MariaDB... + 'driver' => 'mysql', // the database driver, i.e. MySQL, MariaDB, PostgreSQL, SQLite 'host' => 'localhost', // the host of the connection, localhost in most cases unless using an external service 'database' => 'flarum', // the name of the database in the instance 'username' => 'root', // database username @@ -34,3 +34,15 @@ Here's a quick overview of what everything means with an example file: ), ); ``` + +### Maintenance modes + +Flarum has a maintenance mode that can be enabled by setting the `offline` key in the `config.php` file to one of the following values: +* `none` - No maintenance mode. +* `high` - No one can access the forum, not even admins. +* `low` - Only admins can access the forum. +* `safe` - Only admins can access the forum, and no extensions are booted. + +This can also be configured from the admin panel's advanced settings page: + +![Toggle advanced page](https://user-images.githubusercontent.com/20267363/277113270-f2e9c91d-2a29-436b-827f-5c4d20e2ed54.png) diff --git a/docs/contributing.md b/docs/contributing.md index 4fc952fd9..8255caadc 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,7 +4,7 @@ Interested in contributing to Flarum development? That's great! From [opening a Before contributing, please read the [code of conduct](code-of-conduct.md). -This document is a guide for developers who want to contribute code to Flarum. If you're just getting started, we recommend that you read the [Getting Started](/extend/start.md) documentation in the Extension docs to understand a bit more about how Flarum works. +This document is a guide for developers who want to contribute code to Flarum. If you're just getting started, we recommend that you read the [Getting Started](./extend/start.md) documentation in the Extension docs to understand a bit more about how Flarum works. ## Why Contribute to Flarum? @@ -158,7 +158,7 @@ Flarum's CSS classes roughly follow the [SUIT CSS naming conventions](https://gi ### Translations -We use a [standard key format](/extend/i18n.md#appendix-a-standard-key-format) to name translation keys descriptively and consistently. +We use a [standard key format](./extend/i18n.md#appendix-a-standard-key-format) to name translation keys descriptively and consistently. ## Contributor License Agreement @@ -168,4 +168,4 @@ You confirm that you are able to grant us these rights. You represent that You a You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. -The Flarum Foundation acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. \ No newline at end of file +The Flarum Foundation acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. diff --git a/docs/extend/README.md b/docs/extend/README.md index ed31d9f1a..7407779f6 100644 --- a/docs/extend/README.md +++ b/docs/extend/README.md @@ -30,7 +30,7 @@ If you are aiming to address a bug or shortcoming of the core, or of an existing - [Developers explaining their workflow for extension development](https://discuss.flarum.org/d/6320-extension-developers-show-us-your-workflow) - [Extension namespace tips](https://discuss.flarum.org/d/9625-flarum-extension-namespacing-tips) - [Mithril js documentation](https://mithril.js.org/) -- [Laravel API Docs](https://laravel.com/api/8.x/) +- [Laravel API Docs](https://laravel.com/api/11.x/) - [Flarum API Docs](https://api.flarum.org) - [ES6 cheatsheet](https://github.com/DrkSephy/es6-cheatsheet) diff --git a/docs/extend/admin.md b/docs/extend/admin.md index dd0bbfad1..b7f0150f9 100644 --- a/docs/extend/admin.md +++ b/docs/extend/admin.md @@ -1,37 +1,31 @@ # Admin Dashboard -Beta 15 introduced a completely redesigned admin panel and frontend API. It is now easier than ever to add settings or permissions to your extension. +Every extension has a unique page containing information, settings, and the extension's own permissions. -Before beta 15, extension settings were either added in a `SettingsModal` or they added a new page for more complex settings. Now, every extension has a page containing info, settings, and the extension's own permissions. +You can register settings, permissions, or use an entirely custom page based off of the [`ExtensionPage`](https://api.docs.flarum.org/js/master/class/src/admin/components/extensionpage.js~extensionpage) component. -You can simply register settings, extend the base [`ExtensionPage`](https://api.docs.flarum.org/js/master/class/src/admin/components/extensionpage.js~extensionpage), or provide your own completely custom page. +## Admin Extender -## Extension Data API +The admin frontend allows you to add settings and permissions to your extension with very few lines of code, using the `Admin` frontend extender. -This new API allows you to add settings to your extension with very few lines of code. - -### Telling the API about your extension - -Before you can register anything, you need to tell `ExtensionData` what extension it is about to get data for. - -Simply run the `for` function on `app.extensionData` passing in the id of your extension. To find you extension id, take the composer name and replace any slashes with dashes (example: 'fof/merge-discussions' becomes 'fof-merge-discussions'). Extensions with the `flarum-` and `flarum-ext-` will omit those from the name (example: 'webbinaro/flarum-calendar' becomes 'webbinaro-calendar'). - -For the following example, we will use the fictitious extension 'acme/interstellar': +To get started, make sure you have an `admin/extend.js` file: ```js +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; -app.initializers.add('interstellar', function(app) { - - app.extensionData - .for('acme-interstellar') -}); +export default [ + // +] ``` -Once that is done, you can begin adding settings and permissions. +:::info -:::tip Note +Remember to export the `extend` module from your entry `admin/index.js` file: -All registration functions on `ExtensionData` are chainable, meaning you can call them one after another without running `for` again. +```js +export { default as extend } from './extend'; +``` ::: @@ -39,85 +33,151 @@ All registration functions on `ExtensionData` are chainable, meaning you can cal Adding settings fields in this way is recommended for simple items. As a rule of thumb, if you only need to store things in the settings table, this should be enough for you. -To add a field, call the `registerSetting` function after `for` on `app.extensionData` and pass a 'setting object' as the first argument. Behind the scenes `ExtensionData` actually turns your settings into an [`ItemList`](https://api.docs.flarum.org/js/master/class/src/common/utils/itemlist.ts~itemlist), you can pass a priority number as the second argument. +To add a field, call the `setting` method of the `Admin` extender and pass a callback that returns a 'setting object' as the first argument. Behind the scenes, the app turns your settings into an [`ItemList`](https://api.docs.flarum.org/js/master/class/src/common/utils/itemlist.ts~itemlist), you can pass a priority number as the second argument which will determine the order of the settings on the page. Here's an example with a switch (boolean) item: ```js +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; -app.initializers.add('interstellar', function(app) { - - app.extensionData - .for('acme-interstellar') - .registerSetting( - { +return [ + new Extend.Admin() + .setting( + () => ({ setting: 'acme-interstellar.coordinates', // This is the key the settings will be saved under in the settings table in the database. - label: app.translator.trans('acme-interstellar.admin.coordinates_label'), // The label to be shown letting the admin know what the setting does. - help: app.translator.trans('acme-interstellar.admin.coordinates_help'), // Optional help text where a longer explanation of the setting can go. + label: app.translator.trans('acme-interstellar.admin.coordinates_label', {}, true), // The label to be shown letting the admin know what the setting does. + help: app.translator.trans('acme-interstellar.admin.coordinates_help', {}, true), // Optional help text where a longer explanation of the setting can go. type: 'boolean', // What type of setting this is, valid options are: boolean, text (or any other tag type), and select. - }, + }), 30 // Optional: Priority ) -}); +]; ``` If you use `type: 'select'` the setting object looks a little bit different: ```js -{ - setting: 'acme-interstellar.fuel_type', - label: app.translator.trans('acme-interstellar.admin.fuel_type_label'), - type: 'select', - options: { - 'LOH': 'Liquid Fuel', // The key in this object is what the setting will be stored as in the database, the value is the label the admin will see (remember to use translations if they make sense in your context). - 'RDX': 'Solid Fuel', - }, - default: 'LOH', -} +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; + +return [ + new Extend.Admin() + .setting( + () => ({ + setting: 'acme-interstellar.fuel_type', + label: app.translator.trans('acme-interstellar.admin.fuel_type_label', {}, true), + type: 'select', + options: { + 'LOH': 'Liquid Fuel', // The key in this object is what the setting will be stored as in the database, the value is the label the admin will see (remember to use translations if they make sense in your context). + 'RDX': 'Solid Fuel', + }, + default: 'LOH', + }), + ) +]; ``` Also, note that additional items in the setting object will be used as component attrs. This can be used for placeholders, min/max restrictions, etc: ```js -{ - setting: 'acme-interstellar.crew_count', - label: app.translator.trans('acme-interstellar.admin.crew_count_label'), - type: 'number', - min: 1, - max: 10 -} +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; + +return [ + new Extend.Admin() + .setting( + () => ({ + setting: 'acme-interstellar.crew_count', + label: app.translator.trans('acme-interstellar.admin.crew_count_label', {}, true), + type: 'number', + min: 1, + max: 10 + }), + ) +]; ``` If you want to add something to the settings like some extra text or a more complicated input, you can also pass a callback as the first argument that returns JSX. This callback will be executed in the context of [`ExtensionPage`](https://api.docs.flarum.org/js/master/class/src/admin/components/extensionpage.js~extensionpage) and setting values will not be automatically serialized. ```js - -app.initializers.add('interstellar', function(app) { - - app.extensionData - .for('acme-interstellar') - .registerSetting(function () { - if (app.session.user.username() === 'RocketMan') { - - return ( -
-

{app.translator.trans('acme-interstellar.admin.you_are_rocket_man_label')}

- +
+ ); + } + }, + ) +]; +``` + +### Available Setting Types + +This is a list of setting types available by default: + +**Toggle:** `bool` or `checkbox` or `switch` or `boolean` + +**Textarea:** `textarea` + +**Color Picker:** `color-preview` + +**Text Input**: `text` or any HTML input types such as `tel` or `number` + +```ts +{ + setting: 'setting_unique_key', + label: app.translator.trans('acme-interstellar.admin.settings.setting_unique_key', {}, true), + type: 'bool' // Any of the mentioned values above +} +``` + +**Selection:** `select` or `dropdown` or `selectdropdown` + +```ts +{ + setting: 'setting_unique_key', + label: app.translator.trans('acme-interstellar.admin.settings.setting_unique_key', {}, true), + type: 'select', // Any of the mentioned values above + options: { + 'option_key': 'Option Label', + 'option_key_2': 'Option Label 2', + 'option_key_3': 'Option Label 3', + }, + default: 'option_key' +} +``` + +**Image Upload Button:** `image-upload` + +```ts +{ + setting: 'setting_unique_key', + label: app.translator.trans('acme-interstellar.admin.settings.setting_unique_key', {}, true), + type: 'image-upload', + name: 'my_image_name', // The name of the image, this will be used for the request to the backend. + routePath: '/upload-my-image', // The route to upload the image to. + url: () => app.forum.attribute('myImageUrl'), // The URL of the image, this will be used to preview the image. +} ``` ### Registering Permissions -New in beta 15, permissions can now be found in 2 places. Now, you can view each extension's individual permissions on their page. All permissions can still be found on the permissions page. +Permissions can be found in 2 places. You can view each extension's individual permissions on their dedicated page, or you can view all permissions in the main permissions page. -In order for that to happen, permissions must be registered with `ExtensionData`. This is done in a similar way to settings, call `registerPermission`. +In order for that to happen, permissions must be registered using the `permission` method of the `Admin` extender, similar to how settings are registered. Arguments: * Permission object @@ -127,21 +187,22 @@ Arguments: Back to our favorite rocket extension: ```js -app.initializers.add('interstellar', function(app) { +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; - app.extensionData - .for('acme-interstellar') - .registerPermission( - { +return [ + new Extend.Admin() + .permission( + () => ({ icon: 'fas fa-rocket', // Font-Awesome Icon - label: app.translator.trans('acme-interstellar.admin.permissions.fly_rockets_label'), // Permission Label + label: app.translator.trans('acme-interstellar.admin.permissions.fly_rockets_label', {}, true), // Permission Label permission: 'discussion.rocket_fly', // Actual permission name stored in database (and used when checking permission). tagScoped: true, // Whether it be possible to apply this permission on tags, not just globally. Explained in the next paragraph. - }, + }), 'start', // Category permission will be added to on the grid 95 // Optional: Priority - ); -}); + ) +]; ``` If your extension interacts with the [tags extension](https://github.com/flarum/tags) (which is fairly common), you might want a permission to be tag scopable (i.e. applied on the tag level, not just globally). You can do this by including a `tagScoped` attribute, as seen above. Permissions starting with `discussion.` will automatically be tag scoped unless `tagScoped: false` is indicated. @@ -153,17 +214,23 @@ To learn more about Flarum permissions, see [the relevant docs](permissions.md). Remember these functions can all be chained like: ```js -app.extensionData - .for('acme-interstellar') - .registerSetting(...) - .registerSetting(...) - .registerPermission(...) - .registerPermission(...); +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; + +return [ + new Extend.Admin() + .setting(...) + .permission(...) + .permission(...) + .permission(...) + .setting(...) + .setting(...) +]; ``` ### Extending/Overriding the Default Page -Sometimes you have more complicated settings that mess with relationships, or just want the page to look completely different. In this case, you will need to tell `ExtensionData` that you want to provide your own page. Note that `buildSettingComponent`, the util used to register settings by providing a descriptive object, is available as a method on `ExtensionPage` (extending from `AdminPage`, which is a generic base for all admin pages with some util methods). +Sometimes you may have more complicated settings, or just want the page to look completely different. In this case, you will need to tell the `Admin` extender that you want to provide your own page. Note that `buildSettingComponent`, the util used to register settings by providing a descriptive object, is available as a method on `ExtensionPage` (extending from `AdminPage`, which is a generic base for all admin pages with some util methods). Create a new class that extends the `Page` or `ExtensionPage` component: @@ -180,35 +247,89 @@ export default class StarPage extends ExtensionPage { ``` -Then, simply run `registerPage`: +Then, simply use the `page` method of the extender: ```js +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; import StarPage from './components/StarPage'; -app.initializers.add('interstellar', function(app) { - - app.extensionData - .for('acme-interstellar') - .registerPage(StarPage); -}); +return [ + new Extend.Admin() + .page(StarPage) +]; ``` This page will be shown instead of the default. You can extend the [`ExtensionPage`](https://api.docs.flarum.org/js/master/class/src/admin/components/extensionpage.js~extensionpage) or extend the base `Page` and design your own! +### Admin Search + +The admin dashboard has a search bar that allows you to quickly find settings and permissions. If you have used the `Admin.settings` and `Admin.permissions` extender methods, your settings and permissions will be automatically indexed and searchable. However, if you have a custom setting, or custom page that structures its content differently, then you must manually add index entries that reference your custom settings. + +To do this, you can use the `Admin.generalIndexItems` extender method. This method takes a callback that returns an array of index items. Each index item is an object with the following properties: + +```ts +export type GeneralIndexItem = { + /** + * The unique identifier for this index item. + */ + id: string; + /** + * Optional: The tree path to this item, used for grouping in the search results. + */ + tree?: string[]; + /** + * The label to display in the search results. + */ + label: string; + /** + * Optional: The description to display in the search results. + */ + help?: string; + /** + * Optional: The URL to navigate to when this item is selected. + * The default is to navigate to the extension page. + */ + link?: string; + /** + * Optional: A callback that returns a boolean indicating whether this item should be visible in the search results. + */ + visible?: () => boolean; +}; +``` + +Here is an example of how to add an index item: + +```js +import Extend from 'flarum/common/extenders'; +import app from 'flarum/admin/app'; + +return [ + new Extend.Admin() + .generalIndexItems(() => [ + { + id: 'acme-interstellar', + label: app.translator.trans('acme-interstellar.admin.acme_interstellar_label', {}, true), + help: app.translator.trans('acme-interstellar.admin.acme_interstellar_help', {}, true), + }, + ]) +]; +``` + ## Composer.json Metadata -In beta 15, extension pages make room for extra info which is pulled from extensions' composer.json. +Extension pages make room for extra info which is pulled from extensions' composer.json. For more information, see the [composer.json schema](https://getcomposer.org/doc/04-schema.md). -| Description | Where in composer.json | -| --------------------------------- | -------------------------------------- | -| discuss.flarum.org discussion link | "forum" key inside "support" | -| Documentation | "docs" key inside "support" | -| Support (email) | "email" key inside "support" | -| Website | "homepage" key | -| Donate | "funding" key block (Note: Only the first link will be used) | -| Source | "source" key inside "support" | +| Description | Where in composer.json | +|------------------------------------|--------------------------------------------------------------| +| discuss.flarum.org discussion link | "forum" key inside "support" | +| Documentation | "docs" key inside "support" | +| Support (email) | "email" key inside "support" | +| Website | "homepage" key | +| Donate | "funding" key block (Note: Only the first link will be used) | +| Source | "source" key inside "support" | diff --git a/docs/extend/api.md b/docs/extend/api.md index 0f1ad5322..e58294178 100644 --- a/docs/extend/api.md +++ b/docs/extend/api.md @@ -12,348 +12,1022 @@ To use the built-in REST API as part of an integration, see [Consuming the REST Before we go into detail about how to extend Flarum's data API, it's worth thinking about the lifecycle of a typical API request: -![Flarum API Flowchart](/en/img/api_flowchart.png) +![Flarum API Flowchart](../assets/api_flowchart.svg) 1. An HTTP request is sent to Flarum's API. Typically, this will come from the Flarum frontend, but external programs can also interact with the API. Flarum's API mostly follows the [JSON:API](https://jsonapi.org/) specification, so accordingly, requests should follow [said specification](https://jsonapi.org/format/#fetching). -2. The request is run through [middleware](middleware.md), and routed to the proper controller. You can learn more about controllers as a whole on our [routes and content documentation](routes.md). Assuming the request is to the API (which is the case for this section), the controller that handles the request will be a subclass of `Flarum\Api\AbstractSerializeController`. -3. Any modifications done by extensions to the controller via the [`ApiController` extender](#extending-api-controllers) are applied. This could entail changing sort, adding includes, changing the serializer, etc. -4. The `$this->data()` method of the controller is called, yielding some raw data that should be returned to the client. Typically, this data will take the form of a Laravel Eloquent model collection or instance, which has been retrieved from the database. That being said, the data could be anything as long as the controller's serializer can process it. Each controller is responsible for implementing its own `data` method. Note that for `PATCH`, `POST`, and `DELETE` requests, `data` will perform the operation in question, and return the modified model instance. -5. That data is run through any pre-serialization callbacks that extensions register via the [`ApiController` extender](#extending-api-controllers). -6. The data is passed through a [serializer](#serializers), which converts it from the backend, database-friendly format to the JSON:API format expected by the frontend. It also attaches any related objects, which are run through their own serializers. As we'll explain below, extensions can [add / override relationships and attributes](#attributes-and-relationships) at the serialization level. +2. The request is run through [middleware](middleware.md), and routed to the proper API resource endpoint. Each API Resource is distinguished by a unique type and has a set of endpoints. You can read more about them in the below sections. +3. Any modifications done by extensions to the API Resource endpoints via the [`ApiResource` extender](#extending-api-resources) are applied. This could entail changing sort, adding includes, eager loading relations, or executing some logic before and/or after the default implementation runs. +4. The action of the endpoint is called, yielding some raw data that should be returned to the client. Typically, this data will take the form of a Laravel Eloquent model collection or instance, which has been retrieved from the database. That being said, the data could be anything as long as the API resource can process it. There are built-in reusable endpoint for CRUD operations, but custom endpoints can be implemented as well. +5. Any modifications made through the [`ApiResource` extender](#extending-api-resources) to the API resource's fields will be applied. These can include adding new attributes or relationships to serialize, removing existing ones, or changing how the field value is computed. +6. The fields (attributes and relationships) are serialized, converting the data from the backend database-friendly format to the JSON:API format expected by the frontend. 7. The serialized data is returned as a JSON response to the frontend. 8. If the request originated via the Flarum frontend's `Store`, the returned data (including any related objects) will be stored as [frontend models](#frontend-models) in the frontend store. -## API Endpoints +## API Resources We learned how to use models to interact with data, but we still need to get that data from the backend to the frontend. -We do this by writing API Controller [routes](routes.md), which implement logic for API endpoints. +We do this by writing an API Resource for the model, which defines the fields (attributes and relationships) of the model, the endpoints of the resource API, and optionally some extra logic, such as visibility scoping, sorting options, etc. We will learn about this in the next few sections. -As per the JSON:API convention, we'll want to add separate endpoints for each operation we support. Common operations are: +CRUD endpoints are provided by Flarum, so you can simply add them to your API resource's `endpoints()` method. They are: -- Listing instances of a model (possibly including searching/filtering) -- Getting a single model instance -- Creating a model instance -- Updating a model instance -- Deleting a single model instance +- `Index`: Listing many instances of a model (possibly including searching/filtering) +- `Show`: Getting a single model instance +- `Create`: Creating a model instance +- `Update`: Updating a model instance +- `Delete`: Deleting a single model instance -We'll go over each type of controller shortly, but once they're written, you can add these five standard endpoints (or a subset of them) using the `Routes` extender: +:::info -```php - (new Extend\Routes('api')) - ->get('/tags', 'tags.index', ListTagsController::class) - ->get('/tags/{id}', 'tags.show', ShowTagController::class) - ->post('/tags', 'tags.create', CreateTagController::class) - ->patch('/tags/{id}', 'tags.update', UpdateTagController::class) - ->delete('/tags/{id}', 'tags.delete', DeleteTagController::class) +Flarum uses a forked version of Toby Zerner's [json-api-server](https://tobyzerner.github.io/json-api-server/). So some of what is documented there applies in Flarum, but not everything is the same. + +::: + +:::tip [Flarum CLI](https://github.com/flarum/cli) + +You can use the CLI to automatically create your API resource: +```bash +$ flarum-cli make backend api-resource ``` -:::caution +::: -Paths to API endpoints are not arbitrary! To support interactions with frontend models: +***Example:*** if you had a `Label` model, the `LabelResource` you would create could look something like this: -- The path should either be `/prefix/{id}` for get/update/delete, or `/prefix` for list/create. -- the prefix (`tags` in the example above) must correspond to the JSON:API model type. You'll also use this model type in your serializer's `$type` attribute, and when registering the frontend model (`app.store.models.TYPE = MODEL_CLASS`). -- The methods must match the example above. +```php +namespace Acme\Api; -Also, remember that route names (`tags.index`, `tags.show`, etc) must be unique! +use Acme\Label; +use Flarum\Api\Context; +use Flarum\Api\Endpoint; +use Flarum\Api\Resource\AbstractDatabaseResource; +use Flarum\Api\Schema; -::: +/** @extends AbstractDatabaseResource