From cd37cf888e06d325b3c53013d3e387bb86a55919 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:28:11 +0700 Subject: [PATCH 1/7] docs: add avatar drivers guide for User extender Documents the new avatar driver system introduced in flarum/framework#4130. Includes implementation guide, examples, and admin configuration. --- docs/extend/avatar-drivers.md | 305 ++++++++++++++++++++++++++++++++++ sidebars.js | 1 + 2 files changed, 306 insertions(+) create mode 100644 docs/extend/avatar-drivers.md diff --git a/docs/extend/avatar-drivers.md b/docs/extend/avatar-drivers.md new file mode 100644 index 000000000..1976bd57b --- /dev/null +++ b/docs/extend/avatar-drivers.md @@ -0,0 +1,305 @@ +# Avatar Drivers + +Flarum's avatar system provides an extensible driver architecture that allows extensions to integrate custom avatar providers. This enables forums to source user avatars from external services like Gravatar, OAuth providers (Discord, GitHub, Steam), or any custom avatar service. + +By default, Flarum includes two drivers: the Default driver (which falls back to uploaded avatars) and the Gravatar driver (which generates avatars from user email addresses). Extensions can register additional drivers to provide alternative avatar sources for their users. + +## How Avatar Drivers Work + +When Flarum needs to display a user's avatar, it resolves the avatar URL through a fallback chain. Understanding this chain helps you design effective avatar drivers: + +:::info Avatar Resolution Order + +Flarum resolves avatar URLs in the following priority order: + +1. **Custom uploaded avatar** - If the user has uploaded an avatar, use that file +2. **Custom avatar URL** - If a full URL (with `://`) is set in the database +3. **Configured avatar driver** - Call the active driver's `avatarUrl()` method +4. **Null** - Falls back to default UI avatar (initials) + +::: + +Your driver is only called when the user has no uploaded avatar or custom URL. This means: + +- Users can always override your driver by uploading their own avatar +- Your driver provides a fallback for users who haven't customized their avatar +- The `$user->avatar_url` accessor handles the full resolution chain automatically +- You can check if a user has a custom avatar using `$user->original_avatar_url` + +## Creating a Custom Driver + +To create an avatar driver, implement the `Flarum\User\Avatar\DriverInterface` interface. This interface has a single method that returns a URL string or null: + +```php +discord_id; + $avatarHash = $user->discord_avatar_hash; + + // Return null if we don't have the required data + if (!$discordId || !$avatarHash) { + return null; + } + + // Generate Discord CDN URL + return "https://cdn.discordapp.com/avatars/{$discordId}/{$avatarHash}.png?size=200"; + } +} +``` + +The `avatarUrl()` method receives the full User model, allowing you to access any user properties including custom attributes you've added via migrations. + +:::tip Returning Null + +Drivers should return `null` when they cannot provide an avatar URL (e.g., when the user hasn't connected their account). This allows Flarum to fall back to the default avatar display. + +::: + +## Registering Your Driver + +Use the `User` extender to register your avatar driver in your extension's `extend.php` file: + +```php +avatarDriver('discord', DiscordAvatarDriver::class), + + // Other extenders... +]; +``` + +The first parameter is a unique identifier for your driver. This identifier: +- Is stored in the database when an admin selects your driver +- Appears in the admin dashboard dropdown +- Should be unique across all extensions to avoid conflicts + +:::info Driver Identifiers + +Choose a descriptive, unique identifier for your driver (e.g., `'github'`, `'discord'`, `'steam'`). This identifier is saved in the `avatar_driver` setting when an administrator selects it from the admin panel. + +::: + +## Admin Configuration + +Once you register an avatar driver, it automatically appears in the admin dashboard without any additional frontend code. + +**Location:** Admin Dashboard → Basics → Avatar Driver + +The dropdown will only appear when two or more drivers are registered. Administrators can select which driver should be active for users who haven't uploaded custom avatars. + +:::tip Automatic Admin UI + +Flarum automatically adds your driver to the admin settings page when you register it. No JavaScript or frontend code is required for basic driver configuration. + +::: + +The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum uses the Default driver as a fallback. + +## Complete Example: GitHub Avatar Driver + +Here's a complete, working example that demonstrates adding a GitHub avatar driver with database migrations: + +**Migration** (`migrations/YYYY_MM_DD_HHMMSS_add_github_username_to_users.php`): +```php + ['string', 'length' => 255, 'nullable' => true] +]); +``` + +**Driver Class** (`src/Driver/GitHubAvatarDriver.php`): +```php +github_username; + + if (!$username) { + return null; // User hasn't connected GitHub + } + + // GitHub provides user avatars at this URL pattern + // The ?size parameter requests a specific image size + return "https://github.com/{$username}.png?size=200"; + } +} +``` + +**Registration** (`extend.php`): +```php +avatarDriver('github', GitHubAvatarDriver::class), +]; +``` + +This example assumes you have another part of your extension that sets the `github_username` attribute (perhaps through OAuth authentication or a settings page). See the [Models documentation](./models.md) for more information on adding custom user attributes. + +## Advanced Patterns + +### Dependency Injection + +Avatar drivers are resolved through Laravel's service container, which means you can inject dependencies into your driver's constructor: + +```php +settings = $settings; + } + + public function avatarUrl(User $user): ?string + { + // Only provide avatars if the feature is enabled in settings + if (!$this->settings->get('acme.avatars.enabled')) { + return null; + } + + $apiKey = $this->settings->get('acme.avatars.api_key'); + $externalId = $user->external_user_id; + + if (!$apiKey || !$externalId) { + return null; + } + + // Generate URL using configured API endpoint + $endpoint = $this->settings->get('acme.avatars.endpoint', 'https://api.example.com'); + return "{$endpoint}/avatar/{$externalId}?key={$apiKey}&size=200"; + } +} +``` + +You can inject any registered service: settings, cache, database, HTTP client, or your own custom services. + +### Conditional Availability + +Your driver can return `null` based on various conditions: + +```php +public function avatarUrl(User $user): ?string +{ + // Don't provide avatars for suspended users + if ($user->suspended_until && $user->suspended_until->isFuture()) { + return null; + } + + // Don't provide avatars for users in specific groups + if ($user->groups->contains('slug', 'restricted')) { + return null; + } + + // Your avatar generation logic... + return "https://example.com/avatar/{$user->id}"; +} +``` + +:::warning Performance Considerations + +The `avatarUrl()` method is called frequently when rendering user lists, discussion lists, and posts. Avoid making external API calls or database queries directly in this method. + +Instead, store avatar URLs in the database and refresh them periodically using a scheduled job or event listener. This ensures fast page rendering and reduces external API dependencies. + +::: + +:::tip Dependency Injection + +Avatar drivers support full dependency injection through Laravel's service container. You can inject settings, cache, database connections, HTTP clients, or any other registered services into your driver's constructor. + +::: + +## Testing Your Driver + +Follow this checklist to verify your avatar driver works correctly: + +1. **Verify Registration** + - Enable your extension + - Go to Admin → Basics + - Confirm your driver appears in the "Avatar Driver" dropdown + +2. **Select Your Driver** + - Choose your driver from the dropdown + - Save the settings + - Verify the `avatar_driver` setting is saved in the database + +3. **Test Avatar Resolution** + - Create or use a test user account + - Ensure the user has the required data (e.g., Discord ID, GitHub username, etc.) + - View the user's profile and discussion posts + - Confirm the avatar loads from your driver's URL + +4. **Test Fallback Behavior** + - Test with a user who lacks the required data + - Your driver should return `null` + - The user should see Flarum's default avatar (initials) + +5. **Test Custom Upload Override** + - Upload a custom avatar for a test user + - Confirm the uploaded avatar is shown instead of your driver's avatar + - Delete the custom avatar + - Confirm your driver's avatar returns + +6. **Check Developer Console** + - Open browser developer tools → Network tab + - Verify avatar image requests match your driver's URL pattern + - Ensure there are no 404 errors or broken images + +## Related Documentation + +- [Models](./models.md) - Learn how to add custom user attributes for storing external service IDs +- [Settings](./settings.md) - Configure driver settings and API keys +- [Admin Dashboard](./admin.md) - Customize the admin interface for your extension +- [User Model API](https://api.docs.flarum.org/php/master/flarum/user/user) - Full User model reference +- [DriverInterface API](https://api.docs.flarum.org/php/master/flarum/user/avatar/driverinterface) - Complete interface documentation + +For a real-world example, see Flarum's built-in [Gravatar driver](https://github.com/flarum/framework/blob/main/framework/core/src/User/Avatar/GravatarDriver.php) in the framework repository. diff --git a/sidebars.js b/sidebars.js index 80f78afe6..b1db4364a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -128,6 +128,7 @@ module.exports = { items: [ 'extend/api-throttling', 'extend/assets', + 'extend/avatar-drivers', 'extend/console', 'extend/database', 'extend/extending-extensions', From fd5938b43fb6361839100ad17e1bc84419c81d39 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:34:02 +0700 Subject: [PATCH 2/7] wip --- docs/extend/avatar-drivers.md | 176 ---------------------------------- 1 file changed, 176 deletions(-) diff --git a/docs/extend/avatar-drivers.md b/docs/extend/avatar-drivers.md index 1976bd57b..05b1939a8 100644 --- a/docs/extend/avatar-drivers.md +++ b/docs/extend/avatar-drivers.md @@ -118,182 +118,6 @@ Flarum automatically adds your driver to the admin settings page when you regist The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum uses the Default driver as a fallback. -## Complete Example: GitHub Avatar Driver - -Here's a complete, working example that demonstrates adding a GitHub avatar driver with database migrations: - -**Migration** (`migrations/YYYY_MM_DD_HHMMSS_add_github_username_to_users.php`): -```php - ['string', 'length' => 255, 'nullable' => true] -]); -``` - -**Driver Class** (`src/Driver/GitHubAvatarDriver.php`): -```php -github_username; - - if (!$username) { - return null; // User hasn't connected GitHub - } - - // GitHub provides user avatars at this URL pattern - // The ?size parameter requests a specific image size - return "https://github.com/{$username}.png?size=200"; - } -} -``` - -**Registration** (`extend.php`): -```php -avatarDriver('github', GitHubAvatarDriver::class), -]; -``` - -This example assumes you have another part of your extension that sets the `github_username` attribute (perhaps through OAuth authentication or a settings page). See the [Models documentation](./models.md) for more information on adding custom user attributes. - -## Advanced Patterns - -### Dependency Injection - -Avatar drivers are resolved through Laravel's service container, which means you can inject dependencies into your driver's constructor: - -```php -settings = $settings; - } - - public function avatarUrl(User $user): ?string - { - // Only provide avatars if the feature is enabled in settings - if (!$this->settings->get('acme.avatars.enabled')) { - return null; - } - - $apiKey = $this->settings->get('acme.avatars.api_key'); - $externalId = $user->external_user_id; - - if (!$apiKey || !$externalId) { - return null; - } - - // Generate URL using configured API endpoint - $endpoint = $this->settings->get('acme.avatars.endpoint', 'https://api.example.com'); - return "{$endpoint}/avatar/{$externalId}?key={$apiKey}&size=200"; - } -} -``` - -You can inject any registered service: settings, cache, database, HTTP client, or your own custom services. - -### Conditional Availability - -Your driver can return `null` based on various conditions: - -```php -public function avatarUrl(User $user): ?string -{ - // Don't provide avatars for suspended users - if ($user->suspended_until && $user->suspended_until->isFuture()) { - return null; - } - - // Don't provide avatars for users in specific groups - if ($user->groups->contains('slug', 'restricted')) { - return null; - } - - // Your avatar generation logic... - return "https://example.com/avatar/{$user->id}"; -} -``` - -:::warning Performance Considerations - -The `avatarUrl()` method is called frequently when rendering user lists, discussion lists, and posts. Avoid making external API calls or database queries directly in this method. - -Instead, store avatar URLs in the database and refresh them periodically using a scheduled job or event listener. This ensures fast page rendering and reduces external API dependencies. - -::: - -:::tip Dependency Injection - -Avatar drivers support full dependency injection through Laravel's service container. You can inject settings, cache, database connections, HTTP clients, or any other registered services into your driver's constructor. - -::: - -## Testing Your Driver - -Follow this checklist to verify your avatar driver works correctly: - -1. **Verify Registration** - - Enable your extension - - Go to Admin → Basics - - Confirm your driver appears in the "Avatar Driver" dropdown - -2. **Select Your Driver** - - Choose your driver from the dropdown - - Save the settings - - Verify the `avatar_driver` setting is saved in the database - -3. **Test Avatar Resolution** - - Create or use a test user account - - Ensure the user has the required data (e.g., Discord ID, GitHub username, etc.) - - View the user's profile and discussion posts - - Confirm the avatar loads from your driver's URL - -4. **Test Fallback Behavior** - - Test with a user who lacks the required data - - Your driver should return `null` - - The user should see Flarum's default avatar (initials) - -5. **Test Custom Upload Override** - - Upload a custom avatar for a test user - - Confirm the uploaded avatar is shown instead of your driver's avatar - - Delete the custom avatar - - Confirm your driver's avatar returns - -6. **Check Developer Console** - - Open browser developer tools → Network tab - - Verify avatar image requests match your driver's URL pattern - - Ensure there are no 404 errors or broken images - ## Related Documentation - [Models](./models.md) - Learn how to add custom user attributes for storing external service IDs From f9df2e73f6982cf57d69769719fb45eef83af09f Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:35:37 +0700 Subject: [PATCH 3/7] docs: add avatar drivers guide for User extender Documents the new avatar driver system introduced in flarum/framework#4130. Includes implementation guide, examples, and admin configuration. --- docs/extend/{avatar-drivers.md => avatars.md} | 0 sidebars.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/extend/{avatar-drivers.md => avatars.md} (100%) diff --git a/docs/extend/avatar-drivers.md b/docs/extend/avatars.md similarity index 100% rename from docs/extend/avatar-drivers.md rename to docs/extend/avatars.md diff --git a/sidebars.js b/sidebars.js index b1db4364a..8818e3225 100644 --- a/sidebars.js +++ b/sidebars.js @@ -128,7 +128,7 @@ module.exports = { items: [ 'extend/api-throttling', 'extend/assets', - 'extend/avatar-drivers', + 'extend/avatars', 'extend/console', 'extend/database', 'extend/extending-extensions', From a0a1d674bf200347ec90f2fd3451ac9e145020ad Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:40:03 +0700 Subject: [PATCH 4/7] docs: update avatars.md to match Flarum documentation style - Use more direct, technical language - Remove verbose explanations and marketing-style wording - Simplify code examples with minimal comments - Remove redundant sections - Match tone and word choices from mail.md, filesystem.md, notifications.md - Reduce file length from ~300 to ~75 lines --- docs/extend/avatars.md | 91 +++++++++--------------------------------- 1 file changed, 18 insertions(+), 73 deletions(-) diff --git a/docs/extend/avatars.md b/docs/extend/avatars.md index 05b1939a8..7b251cb3c 100644 --- a/docs/extend/avatars.md +++ b/docs/extend/avatars.md @@ -1,38 +1,25 @@ # Avatar Drivers -Flarum's avatar system provides an extensible driver architecture that allows extensions to integrate custom avatar providers. This enables forums to source user avatars from external services like Gravatar, OAuth providers (Discord, GitHub, Steam), or any custom avatar service. +Flarum allows new avatar drivers to be added through extenders. By default, Flarum includes two drivers: the Default driver (which uses uploaded avatars) and the Gravatar driver (which generates avatars from user email addresses). -By default, Flarum includes two drivers: the Default driver (which falls back to uploaded avatars) and the Gravatar driver (which generates avatars from user email addresses). Extensions can register additional drivers to provide alternative avatar sources for their users. +To create your own avatar driver, you will need to create a class implementing `Flarum\User\Avatar\DriverInterface`. This allows extensions to integrate custom avatar providers from external services like Discord, GitHub, Steam, or any custom service. -## How Avatar Drivers Work +## How It Works -When Flarum needs to display a user's avatar, it resolves the avatar URL through a fallback chain. Understanding this chain helps you design effective avatar drivers: - -:::info Avatar Resolution Order - -Flarum resolves avatar URLs in the following priority order: +When Flarum needs to display a user's avatar, it resolves the avatar URL through a fallback chain: 1. **Custom uploaded avatar** - If the user has uploaded an avatar, use that file 2. **Custom avatar URL** - If a full URL (with `://`) is set in the database 3. **Configured avatar driver** - Call the active driver's `avatarUrl()` method 4. **Null** - Falls back to default UI avatar (initials) -::: - -Your driver is only called when the user has no uploaded avatar or custom URL. This means: +Your driver is only called when the user has no uploaded avatar or custom URL. This means users can always override your driver by uploading their own avatar. The `$user->avatar_url` accessor handles this resolution automatically. You can check if a user has a custom avatar using `$user->original_avatar_url`. -- Users can always override your driver by uploading their own avatar -- Your driver provides a fallback for users who haven't customized their avatar -- The `$user->avatar_url` accessor handles the full resolution chain automatically -- You can check if a user has a custom avatar using `$user->original_avatar_url` +## Creating a Driver -## Creating a Custom Driver - -To create an avatar driver, implement the `Flarum\User\Avatar\DriverInterface` interface. This interface has a single method that returns a URL string or null: +The `DriverInterface` has a single method which receives a `User` model and returns either a URL string or null: ```php -discord_id; $avatarHash = $user->discord_avatar_hash; - // Return null if we don't have the required data if (!$discordId || !$avatarHash) { return null; } - // Generate Discord CDN URL return "https://cdn.discordapp.com/avatars/{$discordId}/{$avatarHash}.png?size=200"; } } ``` -The `avatarUrl()` method receives the full User model, allowing you to access any user properties including custom attributes you've added via migrations. +The `avatarUrl()` method receives the full User model, so you can access any user properties including custom attributes you have added via [migrations](database.md). -:::tip Returning Null +Drivers should return `null` when they cannot provide an avatar URL. This allows Flarum to fall back to the default avatar display. -Drivers should return `null` when they cannot provide an avatar URL (e.g., when the user hasn't connected their account). This allows Flarum to fall back to the default avatar display. +## Registering a Driver -::: - -## Registering Your Driver - -Use the `User` extender to register your avatar driver in your extension's `extend.php` file: +To register avatar drivers, use the `Flarum\Extend\User` extender in your extension's `extend.php` file: ```php -avatarDriver('discord', DiscordAvatarDriver::class), - - // Other extenders... + // Other extenders ]; ``` -The first parameter is a unique identifier for your driver. This identifier: -- Is stored in the database when an admin selects your driver -- Appears in the admin dashboard dropdown -- Should be unique across all extensions to avoid conflicts - -:::info Driver Identifiers - -Choose a descriptive, unique identifier for your driver (e.g., `'github'`, `'discord'`, `'steam'`). This identifier is saved in the `avatar_driver` setting when an administrator selects it from the admin panel. - -::: +The first parameter is a unique identifier for your driver. This identifier is stored in the `avatar_driver` setting when an administrator selects it from the admin panel, so it should be unique across all extensions. ## Admin Configuration -Once you register an avatar driver, it automatically appears in the admin dashboard without any additional frontend code. - -**Location:** Admin Dashboard → Basics → Avatar Driver - -The dropdown will only appear when two or more drivers are registered. Administrators can select which driver should be active for users who haven't uploaded custom avatars. - -:::tip Automatic Admin UI - -Flarum automatically adds your driver to the admin settings page when you register it. No JavaScript or frontend code is required for basic driver configuration. - -::: - -The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum uses the Default driver as a fallback. +Once registered, avatar drivers will automatically appear in the admin dashboard under **Basics → Avatar Driver**. The dropdown will only appear when two or more drivers are registered. Administrators can select which driver should be active for users who have not uploaded custom avatars. -## Related Documentation +Flarum automatically adds your driver to the admin settings page when you register it. No additional frontend code is required. -- [Models](./models.md) - Learn how to add custom user attributes for storing external service IDs -- [Settings](./settings.md) - Configure driver settings and API keys -- [Admin Dashboard](./admin.md) - Customize the admin interface for your extension -- [User Model API](https://api.docs.flarum.org/php/master/flarum/user/user) - Full User model reference -- [DriverInterface API](https://api.docs.flarum.org/php/master/flarum/user/avatar/driverinterface) - Complete interface documentation +The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum will use the Default driver as a fallback. -For a real-world example, see Flarum's built-in [Gravatar driver](https://github.com/flarum/framework/blob/main/framework/core/src/User/Avatar/GravatarDriver.php) in the framework repository. +For a real-world example, see Flarum's built-in [Gravatar driver](https://github.com/flarum/framework/blob/main/framework/core/src/User/Avatar/GravatarDriver.php). From c48516cea402e1b381b04c10d37f5f8f7c57231f Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:41:34 +0700 Subject: [PATCH 5/7] wip --- docs/extend/avatars.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/extend/avatars.md b/docs/extend/avatars.md index 7b251cb3c..1e1ffe72e 100644 --- a/docs/extend/avatars.md +++ b/docs/extend/avatars.md @@ -70,5 +70,3 @@ Once registered, avatar drivers will automatically appear in the admin dashboard Flarum automatically adds your driver to the admin settings page when you register it. No additional frontend code is required. The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum will use the Default driver as a fallback. - -For a real-world example, see Flarum's built-in [Gravatar driver](https://github.com/flarum/framework/blob/main/framework/core/src/User/Avatar/GravatarDriver.php). From 65c19034e5c42146b1a31d1ae89a4b25a2bd7dac Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:43:21 +0700 Subject: [PATCH 6/7] wip --- docs/extend/avatars.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/extend/avatars.md b/docs/extend/avatars.md index 1e1ffe72e..7b251cb3c 100644 --- a/docs/extend/avatars.md +++ b/docs/extend/avatars.md @@ -70,3 +70,5 @@ Once registered, avatar drivers will automatically appear in the admin dashboard Flarum automatically adds your driver to the admin settings page when you register it. No additional frontend code is required. The selected driver identifier is stored in the `avatar_driver` setting. If the setting is empty or refers to an unregistered driver, Flarum will use the Default driver as a fallback. + +For a real-world example, see Flarum's built-in [Gravatar driver](https://github.com/flarum/framework/blob/main/framework/core/src/User/Avatar/GravatarDriver.php). From b4b76fa37b1866396550b425e0a2f2f2a010801a Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 30 Dec 2025 09:46:46 +0700 Subject: [PATCH 7/7] wip --- docs/extend/avatars.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extend/avatars.md b/docs/extend/avatars.md index 7b251cb3c..17c3fb2fa 100644 --- a/docs/extend/avatars.md +++ b/docs/extend/avatars.md @@ -32,7 +32,7 @@ class DiscordAvatarDriver implements DriverInterface $discordId = $user->discord_id; $avatarHash = $user->discord_avatar_hash; - if (!$discordId || !$avatarHash) { + if (! $discordId || ! $avatarHash) { return null; }