From a0b1cdfde7257cfda33f01a75817f593abb44b41 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 26 May 2026 20:23:23 -0400 Subject: [PATCH 1/4] Fix technical accuracy issues in Chapter 10 (Users) docs - Fix duplicate 'use use' keyword typo in user-accounts code example - Replace outdated UF4/UF5 TokenRepository path with correct UF6 namespace reference - Update Laravel 8.x doc links to 11.x (model events, soft deletes) - Replace non-existent UserPageAction class reference with correct UsersSprunjeAction - Add missing semicolons to PHP property declarations in access-control examples - Add missing never() callback to access conditions table - Fix broken anchor #Accessconditions -> #access-conditions in groups page - Fix critical lastActivity() API misuse: method returns ?Activity, not a Builder - Fix logging example missing required user_id context key (causes LogicException) - Fix incorrect note claiming lastActivity() with parens returns a relationship - Fix DI override example: use UserActivityLoggerInterface::class key, correct constructor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../6.0/10.users/01.user-accounts/docs.md | 10 ++--- .../6.0/10.users/02.access-control/docs.md | 7 ++-- app/pages/6.0/10.users/03.groups/docs.md | 2 +- .../6.0/10.users/04.activity-logging/docs.md | 38 ++++++++++++------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/pages/6.0/10.users/01.user-accounts/docs.md b/app/pages/6.0/10.users/01.user-accounts/docs.md index 6ac643f0..6f7d0728 100644 --- a/app/pages/6.0/10.users/01.user-accounts/docs.md +++ b/app/pages/6.0/10.users/01.user-accounts/docs.md @@ -16,7 +16,7 @@ To do this, the client tells the server their **identity** (email or username) a When the server receives a subsequent request, it simply needs to check that the client's session is associated with a valid user identity. In UserFrosting, this can be easily handled by applying the `AuthGuard` middleware to the protected route: ```php -use use UserFrosting\Sprinkle\Account\Authenticate\AuthGuard; +use UserFrosting\Sprinkle\Account\Authenticate\AuthGuard; // ... @@ -141,7 +141,7 @@ Sometimes, a user registers but then loses the verification email or fails to ve Verification requests expire after a period of time specified in `verification.timeout`. The default is 10800 seconds (3 hours). -For the precise implementation of the password reset and account verification resend features, see `sprinkles/account/src/Repository/TokenRepository`. +For the precise implementation of the password reset and account verification resend features, see the `UserFrosting\Sprinkle\Account\Controller\` namespace in the Account sprinkle. ### Account settings and profile page @@ -175,7 +175,7 @@ $this->logger->info("User {$currentUser->user_name} updated their profile settin ]); ``` -Every logged event includes the user's id, IP address, timestamp, an event type (e.g., `update_profile_settings`), and a description of the event. You may add additional logging directly in your controllers, or you can attach them to Laravel [model events](https://laravel.com/docs/8.x/eloquent#events) so that they occur automatically when the model is created/saved/updated/deleted. +Every logged event includes the user's id, IP address, timestamp, an event type (e.g., `update_profile_settings`), and a description of the event. You may add additional logging directly in your controllers, or you can attach them to Laravel [model events](https://laravel.com/docs/11.x/eloquent#events) so that they occur automatically when the model is created/saved/updated/deleted. ## Account administration @@ -183,7 +183,7 @@ Depending on the permissions you have assigned, users with the "Administrator" r ### View a list of users -The user listing page is available at `/users` (`UserFrosting\Sprinkle\Admin\Controller\User\UserPageAction`). The actual table of users is implemented through a combination of the page itself, which generates the "skeleton" of the table, and AJAX calls to the `/api/users` route, which fetches the actual data as a JSON object (`UserFrosting\Sprinkle\Admin\Controller\User\UserPageAction::sprunje`). This allows the page to efficiently retrieve paginated, filtered, sorted results without needing to reload the page. +The user listing page is available at `/admin/users`. The actual table of users is implemented through a combination of the Vue.js frontend, which generates the page and table skeleton, and AJAX calls to the `/api/users` route, which fetches the actual data as a JSON object (`UserFrosting\Sprinkle\Admin\Controller\User\UsersSprunjeAction`). This allows the page to efficiently retrieve paginated, filtered, sorted results without needing to reload the page. See [Data Sprunjing](/database/data-sprunjing) for more details on how this works. @@ -223,7 +223,7 @@ The administrator can later re-enable the account, if desired. User accounts can be deleted from the user profile page, or the user dropdown menu in the users table. -Deleting user accounts presents a problem because the user may have related data in the database that would become orphaned, potentially breaking other functionality in your site. For this reason, UserFrosting performs [soft deletes](https://laravel.com/docs/8.x/eloquent#soft-deleting) by default. The user record is not actually deleted, but instead a `deleted_at` timestamp is added to the record and the user is no longer able to sign in. Deleted users are also excluded from all queries unless the `withTrashed` method is added to the Eloquent query. Related entities (activities, roles, etc) are left alone. +Deleting user accounts presents a problem because the user may have related data in the database that would become orphaned, potentially breaking other functionality in your site. For this reason, UserFrosting performs [soft deletes](https://laravel.com/docs/11.x/eloquent#soft-deleting) by default. The user record is not actually deleted, but instead a `deleted_at` timestamp is added to the record and the user is no longer able to sign in. Deleted users are also excluded from all queries unless the `withTrashed` method is added to the Eloquent query. Related entities (activities, roles, etc) are left alone. If you really want to completely remove the user from the database, you can call the `User::forceDelete` method in your controller logic. diff --git a/app/pages/6.0/10.users/02.access-control/docs.md b/app/pages/6.0/10.users/02.access-control/docs.md index fb278c98..26f8726c 100644 --- a/app/pages/6.0/10.users/02.access-control/docs.md +++ b/app/pages/6.0/10.users/02.access-control/docs.md @@ -46,10 +46,10 @@ This can be done by calling the `checkAccess` method of the `AuthorizationManage ```php #[\DI\Attribute\Inject] -protected Authenticator $authenticator +protected Authenticator $authenticator; #[\DI\Attribute\Inject] -protected AuthorizationManager $authorizer +protected AuthorizationManager $authorizer; // ... @@ -64,7 +64,7 @@ Or simply use the `checkAccess` method of the `Authenticator` service, which is ```php #[\DI\Attribute\Inject] -protected Authenticator $authenticator +protected Authenticator $authenticator; // ... if (!$this->authenticator->checkAccess('uri_users')) { @@ -120,6 +120,7 @@ UserFrosting ships with a number of predefined access condition callbacks, which | Callback | Description | | --------------------------------- | -------------------------------------------------------------------------------------------- | | `always()` | Unconditionally grant permission - use carefully! | +| `never()` | Unconditionally deny permission. | | `equals($val1, $val2)` | Check if the specified values are identical to one another (strict comparison). | | `equals_num($val1, $val2)` | Check if the specified values are numeric, and if so, if they are equal to each other. | | `has_role($user_id, $role_id)` | Check if the specified user (by `$user_id`) has a particular role. | diff --git a/app/pages/6.0/10.users/03.groups/docs.md b/app/pages/6.0/10.users/03.groups/docs.md index eabcda10..7f304be3 100644 --- a/app/pages/6.0/10.users/03.groups/docs.md +++ b/app/pages/6.0/10.users/03.groups/docs.md @@ -11,6 +11,6 @@ To set the default group for newly registered users, use the `site.registration. #### Conditioning permissions on group membership -Groups are not directly associated with roles or permissions. A user in Baltimore and a user in London could have the exact same roles, but be in different groups. However, group membership can still influence a user's effective permissions _indirectly_ through a permission's [access conditions](/users/access-control#Accessconditions). +Groups are not directly associated with roles or permissions. A user in Baltimore and a user in London could have the exact same roles, but be in different groups. However, group membership can still influence a user's effective permissions _indirectly_ through a permission's [access conditions](/users/access-control#access-conditions). For example, consider the default permission `view_group_field` with condition `equals_num(self.group_id,group.id) && in(property,['name','icon','slug','description','users'])`. A Baltimore user and a London user might both have this same permission, for example through the "Group Administrator" role. But since the condition requires that the user's `group_id` match the target `group.id`, the Baltimore user will only see group fields for the Baltimore group, and the London user will only see group fields for the London group. diff --git a/app/pages/6.0/10.users/04.activity-logging/docs.md b/app/pages/6.0/10.users/04.activity-logging/docs.md index e458298b..6cb4e667 100644 --- a/app/pages/6.0/10.users/04.activity-logging/docs.md +++ b/app/pages/6.0/10.users/04.activity-logging/docs.md @@ -41,13 +41,14 @@ The following activity types are logged by the core UserFrosting features: In your controller methods, simply call the `info` method on the `userActivityLogger` service to log additional activities: ```php -/** @var \UserFrosting\Sprinkle\Account\Log\UserActivityLogger $userActivityLogger */ -$userActivityLogger->info("User {$currentUser->user_name} adopted a new owl '{$owl->name}'.", [ - 'type' => 'adopt_owl' +/** @var \UserFrosting\Sprinkle\Account\Log\UserActivityLoggerInterface $logger */ +$logger->info("User {$currentUser->user_name} adopted a new owl '{$owl->name}'.", [ + 'type' => 'adopt_owl', + 'user_id' => $currentUser->id, ]); ``` -The first parameter is the activity description. The second parameter contains an array, which should have a `type` key defined. The value of this key decides the activity type that will be logged. Note that these activity types are not defined anywhere explicitly - they are stored in the database as plain text and you may create new types on the fly when you log an activity. +The first parameter is the activity description. The second parameter contains an array with two required keys: `type` (which determines the activity type stored in the database) and `user_id` (the ID of the user performing the activity). Note that activity types are stored as plain text — you may create new types on the fly when you log an activity. > [!NOTE] > In general, you will probably want to log user activities at the end of the controller method, after the user's activity has completed successfully. However, you may choose to write to this log at any point in your code. @@ -73,18 +74,18 @@ $lastActivity = $user->lastActivity; ``` > [!NOTE] -> Notice that we reference this as an model _property_, rather than calling it as a method. If we called `$user->lastActivity()` (with parentheses) instead, it would return the _relationship_ rather than the model itself. +> Notice that we reference this as a model _property_, rather than calling it as a method. If we called `$user->lastActivity()` (with parentheses) instead, it would return an `?Activity` result directly (without the Eloquent attribute accessor), which behaves the same way but bypasses Laravel's magic property caching. ### Getting a user's last activity by type If you want to get the last activity _of a specific type_, use the `lastActivity` method, with the type as argument: ```php -$lastSignIn = $user->lastActivity('sign_in')->get(); +$lastSignIn = $user->lastActivity('sign_in'); ``` > [!NOTE] -> Since `lastActivity(...)` returns a `Builder` object, we need to call `get` to return the actual result. +> `lastActivity($type)` returns a single `?Activity` model (or `null` if no matching activity exists), not a query Builder. No further chaining is needed. ### Getting the time of a user's last activity @@ -114,15 +115,26 @@ $usersWithActivities = User::joinLastActivity()->get(); By default, UserFrosting implements a [custom Monolog handler](https://github.com/Seldaek/monolog/blob/master/doc/04-extending.md), `UserFrosting\Sprinkle\Account\Log\UserActivityDatabaseHandler`, that sends user activity logs to the `activities` database table. -This is all assembled in the `LoggersService` service. If you'd prefer, you can [extend or override](/dependency-injection/extending-services) the `\UserFrosting\Sprinkle\Account\Log\UserActivityLogger` class reference in the DI Container to add additional handlers, or even completely replace the custom handler altogether. For example, to replace the `UserActivityDatabaseHandler` with `StreamHandler` : +This is all assembled in the `LoggersService` service. If you'd prefer, you can [extend or override](/dependency-injection/extending-services) the `UserActivityLoggerInterface` binding in the DI Container to add additional handlers, or even completely replace the default handler. For example, to log to a file instead of the database, create a custom logger class and bind it: ```php -UserActivityLogger::class => function () { - $handler = new StreamHandler('log://activities.log'); - $logger = new UserActivityLogger('userActivity', [$handler]); +use Monolog\Handler\StreamHandler; +use UserFrosting\Sprinkle\Account\Log\UserActivityLoggerInterface; +use UserFrosting\Sprinkle\Core\Log\Logger; + +final class FileActivityLogger extends Logger implements UserActivityLoggerInterface +{ + public function __construct() + { + parent::__construct(new StreamHandler('/path/to/activities.log'), 'userActivity'); + } +} +``` + +Then register it in a service provider: - return $logger; -}, +```php +UserActivityLoggerInterface::class => \DI\autowire(FileActivityLogger::class), ``` See the [Monolog documentation](https://seldaek.github.io/monolog/) for more details. From fe3dc723661d75a26a4bf2442e368f7030112d2c Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 26 May 2026 21:02:07 -0400 Subject: [PATCH 2/4] Pre-release doc review: fix technical accuracy across 6.0 chapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes identified during full pre-release documentation review: - Installation: remove beta stability flag and TODO comments, fix npm script (dev → vite:dev), fix Docker Compose V2 syntax (docker-compose → docker compose), rename docker-compose.yml → compose.yaml, fix MySQL port typo (8593 → 8503), fix composer update → install - Structure: Symfony Console 5 → 6 with updated link; three sprinkles (not four); fix i81n → I18n typo - Sprinkles: fix /app/tests path, add MarkdownExtensionRecipe to optional recipes table, fix vendor path - DI: fix possessive its/it's, update stale Laravel docs links (8.x → versionless), fix getServices() description - Routes: fix Slim 3 array $args pattern, fix return $response anti-pattern, fix @return docblock, remove TODO comment, fix validate() return type description - Pages: fix {% urlFor %} → {{ urlFor }} (Twig function vs tag), align template path prose/code - Config: fix nested array merge example (timezone stays under php key) - i18n: fix locale paths (UF4/5 → UF6 format), fix es_ES typo, update monorepo links - Mail: fix send() bool parameter semantics - UI Theming: fix $validate() return type handling; fix AlertInterface import path - Advanced: fix PHP namespace leading backslash, implements → extends for ExceptionHandler, fix ResourceStream syntax errors, replace broken middleware image with link, add obsolete: true to webpack-encore sub-pages, fix alert-stream deprecation claim - Troubleshooting: update Postman link, remove commented-out UF4 content Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- app/pages/6.0/01.quick-start/docs.md | 4 +-- .../6.0/03.structure/02.dependencies/docs.md | 8 ++--- .../6.0/03.structure/03.framework/docs.md | 2 +- .../6.0/03.structure/04.sprinkles/docs.md | 5 +-- .../01.native/02.install/docs.md | 5 ++- .../02.environment/02.docker/docs.md | 34 +++++++++---------- app/pages/6.0/05.sprinkles/02.content/docs.md | 4 +-- app/pages/6.0/05.sprinkles/03.recipe/docs.md | 17 ++++++++++ .../6.0/05.sprinkles/04.customize/docs.md | 2 +- .../02.the-di-container/docs.md | 2 +- .../03.default-services/docs.md | 4 +-- .../04.adding-services/docs.md | 2 +- .../03.front-controller/docs.md | 6 ++-- .../04.controller-classes/docs.md | 5 ++- .../05.registering-routes/docs.md | 5 ++- .../06.client-input/01.validation/docs.md | 2 +- .../06.client-input/04.ajax/docs.md | 4 +-- .../03.sprinkle-templates/docs.md | 2 +- .../04.filters-and-functions/docs.md | 7 ++-- .../09.configuration/02.config-files/docs.md | 8 +++-- .../6.0/10.users/01.user-accounts/docs.md | 4 +-- .../6.0/12.mail/01.the-mailer-service/docs.md | 2 +- app/pages/6.0/13.i18n/01.introduction/docs.md | 12 +++---- app/pages/6.0/13.i18n/03.translator/docs.md | 8 +++-- app/pages/6.0/13.i18n/chapter.md | 2 +- app/pages/6.0/17.ui-theming/05.forms/docs.md | 6 ++-- app/pages/6.0/17.ui-theming/06.alerts/docs.md | 2 +- .../6.0/18.advanced/01.custom-models/docs.md | 4 +-- .../6.0/18.advanced/03.error-handling/docs.md | 4 +-- .../6.0/18.advanced/06.alert-stream/docs.md | 6 ++-- app/pages/6.0/18.advanced/07.locator/docs.md | 4 +-- .../6.0/18.advanced/11.middlewares/docs.md | 3 +- .../12.webpack-encore/01.basic-usage/docs.md | 1 + .../02.asset-bundles/docs.md | 1 + .../12.webpack-encore/03.advance-use/docs.md | 1 + .../21.troubleshooting/01.debugging/docs.md | 2 +- .../03.common-problems/docs.md | 32 ----------------- 37 files changed, 110 insertions(+), 112 deletions(-) diff --git a/app/pages/6.0/01.quick-start/docs.md b/app/pages/6.0/01.quick-start/docs.md index 87d7c085..96c06f68 100644 --- a/app/pages/6.0/01.quick-start/docs.md +++ b/app/pages/6.0/01.quick-start/docs.md @@ -1,6 +1,6 @@ --- title: Quick Start Guide -description: The official documentation for UserFrosting, a PHP framework and full-featured user management application. +description: Get UserFrosting up and running quickly. This guide walks you through installing and launching UserFrosting on your local machine. --- UserFrosting is a free, open-source jumping-off point for building user-centered web applications with PHP and Javascript. It comes with a sleek, modern interface, basic user account features, and an administrative user management system - all fully functioning out of the box. @@ -27,7 +27,7 @@ UserFrosting has a few system requirements. You need to make sure your local Use Use Composer to create a new project with the latest version of UserFrosting into a `UserFrosting` folder. This will clone the skeleton repository and run the installation process. ```bash -composer create-project userfrosting/userfrosting UserFrosting "^6.0-beta" +composer create-project userfrosting/userfrosting UserFrosting "^6.0" ``` > [!TIP] diff --git a/app/pages/6.0/03.structure/02.dependencies/docs.md b/app/pages/6.0/03.structure/02.dependencies/docs.md index 9ae94c07..f229ac17 100644 --- a/app/pages/6.0/03.structure/02.dependencies/docs.md +++ b/app/pages/6.0/03.structure/02.dependencies/docs.md @@ -10,10 +10,10 @@ While UserFrosting uses dozens of dependencies, here's a rundown of the most imp ## Slim 4 **[Slim](https://www.slimframework.com)** is a PHP _micro framework_ that helps you quickly write simple yet powerful web applications and APIs. Slim is the backbone of UserFrosting. To be more precise, **UserFrosting _is_ a Slim Application**! -Except for the Bakery system (which uses _[Symfony Console](#symfony-console-5)_), UserFrosting uses Slim at every level to perform middleware management, route collections, and everything else needed to actually display a web page. +Except for the Bakery system (which uses _[Symfony Console](#symfony-console-6)_), UserFrosting uses Slim at every level to perform middleware management, route collections, and everything else needed to actually display a web page. ## PHP-DI 7 -**[PHP-DI](https://php-di.org)** is a _dependency injection container_. Dependency injection is one of the fundamental pillars of modern object-oriented software design. It is used extensively throughout UserFrosting to glue all services together while maintaining great flexibility to extend the basics functionalities of UserFrosting to create your own project. We'll explain dependency injection in detail in a later chapter. For now, it's only important to note **PHP-DI** is the dependency manager used by UserFrosting 5 to handle all dependency injection. +**[PHP-DI](https://php-di.org)** is a _dependency injection container_. Dependency injection is one of the fundamental pillars of modern object-oriented software design. It is used extensively throughout UserFrosting to glue all services together while maintaining great flexibility to extend the basics functionalities of UserFrosting to create your own project. We'll explain dependency injection in detail in a later chapter. For now, it's only important to note **PHP-DI** is the dependency manager used by **UserFrosting** to handle all dependency injection. ## Eloquent (Laravel 10) **[Eloquent](https://laravel.com/docs/10.x/eloquent)** is part of the Laravel Framework. Eloquent makes it enjoyable to interact with a database. When using Eloquent, each database table has a corresponding "Model" that is used to interact with that table. In addition to retrieving records from the database table, Eloquent models allow you to insert, update, and delete records from the table as well. @@ -26,8 +26,8 @@ Eloquent is one of the most powerful and easy to use tools available to interact ## Twig 3 **[Twig](https://twig.symfony.com/doc/)** is a flexible, fast, and secure template engine for PHP. Initially developed for the Symfony framework, Twig is easy to use. Twig provides the necessary tools to use the data generated by PHP and render the HTML page the end user gets to see. -## Symfony Console 5 -**[Symfony Console](https://symfony.com/doc/5.4/components/console.html)** eases the creation of beautiful and testable command line interfaces. This is used to power the **Bakery** command line interface tool used by UserFrosting. +## Symfony Console 6 +**[Symfony Console](https://symfony.com/doc/6.4/components/console.html)** eases the creation of beautiful and testable command line interfaces. This is used to power the **Bakery** command line interface tool used by UserFrosting. ## Vite **[Vite](https://vitejs.dev)** is a modern build tool that provides lightning-fast development with Hot Module Replacement (HMR), instant server start, and optimized production builds. Vite is used by UserFrosting to build and serve all frontend assets including CSS, JavaScript, TypeScript, and Vue components. diff --git a/app/pages/6.0/03.structure/03.framework/docs.md b/app/pages/6.0/03.structure/03.framework/docs.md index c7839b00..52c5ed76 100644 --- a/app/pages/6.0/03.structure/03.framework/docs.md +++ b/app/pages/6.0/03.structure/03.framework/docs.md @@ -14,6 +14,6 @@ The documentation for each part is embedded in the next chapters, but you can st - [Cache](https://github.com/userfrosting/framework/tree/main/src/Cache) : Wrapper function for Laravel cache system for easier integration of the cache system in standalone projects. - [Config](https://github.com/userfrosting/framework/tree/main/src/Config) : Configuration files aggregator - [Fortress](https://github.com/userfrosting/framework/tree/main/src/Fortress) : A schema-driven system for elegant whitelisting, transformation and validation of user input, on both the client and server sides, from a unified set of rules. - - [i81n](https://github.com/userfrosting/framework/tree/main/src/I18n) : The I18n module handles translation tasks. + - [I18n](https://github.com/userfrosting/framework/tree/main/src/I18n) : The I18n module handles translation tasks. - [Session](https://github.com/userfrosting/framework/tree/main/src/Session) : PHP Session wrapper - [UniformResourceLocator](https://github.com/userfrosting/framework/tree/main/src/UniformResourceLocator) : The Uniform Resource Locator module handles resource aggregation and stream wrapper diff --git a/app/pages/6.0/03.structure/04.sprinkles/docs.md b/app/pages/6.0/03.structure/04.sprinkles/docs.md index 0cdabe30..9bd46371 100644 --- a/app/pages/6.0/03.structure/04.sprinkles/docs.md +++ b/app/pages/6.0/03.structure/04.sprinkles/docs.md @@ -13,7 +13,7 @@ Your app can have as many sprinkles as you want. A sprinkle could even depend on ## Bundled Sprinkles -A default UserFrosting installation comes with **four** sprinkles, each of which will be downloaded by [Composer](/installation/requirements/essential-tools-for-php#composer) in the `/vendor` directory during installation. +A default UserFrosting installation comes with **three** sprinkles, each of which will be downloaded by [Composer](/installation/requirements/essential-tools-for-php#composer) in the `/vendor` directory during installation. Because UserFrosting is modular, you can decide to use these bundled sprinkles or not. You may or may not need the functionality each provides in your app. We'll go over how to enable and disable them [later](/sprinkles/recipe#removing-default-sprinkles). For now, let's focus on their features. @@ -33,7 +33,4 @@ The **Admin** sprinkle contains the routes and controllers to implement the admi The Admin sprinkle depends on the Core and Account sprinkles. -### Pink Cupcake Theme -The **Pink Cupcake** theme contains all the Twig templates and frontend assets built with [UiKit](https://getuikit.com). It provides a modern, responsive interface with Vue 3 components for interactive features. -The Pink Cupcake theme depends on the Core and Account sprinkles. diff --git a/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md b/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md index 2d0110be..ef1d2755 100644 --- a/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md +++ b/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md @@ -9,9 +9,8 @@ Now that your local development environment is setup and ready to go, it's final Use Composer to create an empty project with the latest version of UserFrosting skeleton into a new `UserFrosting` folder: - ```bash -composer create-project userfrosting/userfrosting UserFrosting "^6.0" --stability=beta +composer create-project userfrosting/userfrosting UserFrosting "^6.0" ``` > [!TIP] @@ -52,7 +51,7 @@ This starts the backend on [http://localhost:8080](http://localhost:8080). **Terminal 2 - Start the Vite dev server:** ```bash -npm run dev +npm run vite:dev ``` This starts the Vite development server with Hot Module Replacement (HMR) for instant frontend updates. diff --git a/app/pages/6.0/04.installation/02.environment/02.docker/docs.md b/app/pages/6.0/04.installation/02.environment/02.docker/docs.md index c4d09a55..bf78897f 100644 --- a/app/pages/6.0/04.installation/02.environment/02.docker/docs.md +++ b/app/pages/6.0/04.installation/02.environment/02.docker/docs.md @@ -41,9 +41,9 @@ First, you'll need to install Docker. Just follow the installation instructions For the next part, you'll need to use the command line. We'll use Composer (through a Docker image) to create an empty project, with the latest version of the UserFrosting skeleton, into a new `UserFrosting` subdirectory: - + ```bash -docker run --rm -it -v "$(pwd):/app" composer create-project userfrosting/userfrosting UserFrosting "^6.0-beta" --no-scripts --no-install --ignore-platform-reqs +docker run --rm -it -v "$(pwd):/app" composer create-project userfrosting/userfrosting UserFrosting "^6.0" --no-scripts --no-install --ignore-platform-reqs ``` > [!TIP] @@ -65,7 +65,7 @@ Now it's simply a matter of navigating to the directory containing the source co 2. Build each of the Docker Containers (this might take a while): ```bash - docker-compose build --no-cache + docker compose build --no-cache ``` 3. Copy the `.env` template @@ -76,7 +76,7 @@ Now it's simply a matter of navigating to the directory containing the source co 4. Start each Docker Container: ```bash - docker-compose up -d + docker compose up -d ``` 5. Set some directory permissions (you may have to enter your root password): @@ -90,13 +90,13 @@ Now it's simply a matter of navigating to the directory containing the source co 6. Install PHP dependencies: ```bash - docker-compose exec app composer update + docker compose exec app composer install ``` 7. Install UserFrosting (database configuration and migrations, creation of admin user, etc.). You'll need to provide info to create the admin user: ```bash - docker-compose exec app php bakery bake + docker compose exec app php bakery bake ``` Now visit [http://localhost:8080](http://localhost:8080) to see your UserFrosting homepage! @@ -108,12 +108,12 @@ You should see the default UserFrosting pages and be able to log in with the new To stop the containers, run: ```bash -docker-compose stop +docker compose stop ``` ## Mailpit -UserFrosting's default `docker-compose.yml` file contains a service entry for [Mailpit](https://github.com/axllent/mailpit). Mailpit intercepts emails sent by your application during local development and provides a convenient web interface so that you can preview your email messages in your browser. +UserFrosting's default `compose.yaml` file contains a service entry for [Mailpit](https://github.com/axllent/mailpit). Mailpit intercepts emails sent by your application during local development and provides a convenient web interface so that you can preview your email messages in your browser. While UserFrosting is running, you may access the Mailpit web interface at: [http://localhost:8025](http://localhost:8025). @@ -124,7 +124,7 @@ Every Bakery command needs to be wrapped in Docker Compose syntax, since you nee For example: ```bash -docker-compose exec app php bakery ... +docker compose exec app php bakery ... ``` ## Working with the Containers @@ -132,38 +132,38 @@ docker-compose exec app php bakery ... If you need to stop the UserFrosting Docker containers, change to your UserFrosting directory and run: ```bash -docker-compose stop +docker compose stop ``` To start the containers again, change to your UserFrosting directory and run: ```bash -docker-compose up -d +docker compose up -d ``` If you need to purge your Docker containers (this will not delete any source files or sprinkles, but will empty the database), run: ```bash -docker-compose down --remove-orphans +docker compose down --remove-orphans ``` And then start the installation process again. ## Advanced configuration -At the heart of everything is the `docker-compose.yml` file. If you're experienced with Docker and Docker Compose, this is where you can customize your Docker experience. For example, you can customize the port each service runs on. Since the file is located in *your sprinkle* (your app), it's possible to save this file in your repository. +At the heart of everything is the `compose.yaml` file. If you're experienced with Docker and Docker Compose, this is where you can customize your Docker experience. For example, you can customize the port each service runs on. Since the file is located in *your sprinkle* (your app), it's possible to save this file in your repository. -The `docker-compose.yml` file also contains the MySQL database and Mail environment variables. Since these variables are defined globally inside the container, they don't need to be redefined inside the `.env` file. +The `compose.yaml` file also contains the MySQL database and Mail environment variables. Since these variables are defined globally inside the container, they don't need to be redefined inside the `.env` file. > [!WARNING] > If you have **multiple** instances of UserFrosting on your computer, **they will share the same configuration by default**. This means: > 1. You can't run multiple Docker instances of UserFrosting *simultaneously* with the default configuration, as ports will conflict. > 2. Both instances will share the same database. > -> If you wish to run multiple instances of UserFrosting on the same computer with Docker, you must edit the `docker-compose.yml` for each instance and change the ports and database volumes/database names. +> If you wish to run multiple instances of UserFrosting on the same computer with Docker, you must edit the `compose.yaml` for each instance and change the ports and database volumes/database names. > [!NOTE] -> An "*address already in use*" error can be thrown if a port defined in `docker-compose.yml` is already used on your system. For example, if Mailpit is installed locally and running on the default port, you'll get an "address already in use" error when running Docker. This can be solved by changing the port in `docker-compose.yml`. +> An "*address already in use*" error can be thrown if a port defined in `compose.yaml` is already used on your system. For example, if Mailpit is installed locally and running on the default port, you'll get an "address already in use" error when running Docker. This can be solved by changing the port in `compose.yaml`. ## Production Environment @@ -171,7 +171,7 @@ The `docker-compose.yml` file also contains the MySQL database and Mail environm You may be tempted to use this configuration in production, but it has not been security-hardened. For example: -- The database is exposed on port 8593 so you can access MySQL using your favorite client at `localhost:8593`. However, the way Docker exposes ports bypasses common firewalls like `ufw`. This should not be exposed in production. +- The database is exposed on port 8503 so you can access MySQL using your favorite client at `localhost:8503`. However, the way Docker exposes ports bypasses common firewalls like `ufw`. This should not be exposed in production. - Database credentials are hard-coded, which is not secure. - File permissions may be more permissive than necessary. - HTTPS is not implemented. diff --git a/app/pages/6.0/05.sprinkles/02.content/docs.md b/app/pages/6.0/05.sprinkles/02.content/docs.md index e6b9aee7..bb6fd4ee 100644 --- a/app/pages/6.0/05.sprinkles/02.content/docs.md +++ b/app/pages/6.0/05.sprinkles/02.content/docs.md @@ -108,9 +108,9 @@ The `storage` directory is used to store files managed by Filesystem service. Th To separate content and logic, UserFrosting uses the popular [Twig](http://twig.symfony.com/) templating engine. Since Twig has its own system for [loading templates](http://twig.symfony.com/doc/api.html#built-in-loaders), UserFrosting builds upon this to allow overriding templates in sprinkles. See [Templating with Twig](/pages-and-layout) for more information on how Twig is integrated into UserFrosting. -### /app/test +### /app/tests -The `test` directory is similar to `/src`, but for your [Tests](/testing). +The `tests` directory is similar to `/src`, but for your [Tests](/testing). ### /app/.env diff --git a/app/pages/6.0/05.sprinkles/03.recipe/docs.md b/app/pages/6.0/05.sprinkles/03.recipe/docs.md index 7adbec23..a6595cc7 100644 --- a/app/pages/6.0/05.sprinkles/03.recipe/docs.md +++ b/app/pages/6.0/05.sprinkles/03.recipe/docs.md @@ -199,6 +199,7 @@ The available sub-recipes includes: | [MiddlewareRecipe](#middlewarerecipe) | Registering [Middlewares](/advanced/middlewares) | | [EventListenerRecipe](#eventlistenerrecipe) | Registering [Event Listeners](/advanced/events) | | [TwigExtensionRecipe](#twigextensionrecipe) | Registering [Twig Extension](/pages-and-layout/filters-and-functions#extending-twig-extensions) | +| [MarkdownExtensionRecipe](#markdownextensionrecipe) | Register custom CommonMark extensions | Your recipe simply needs to implement the corresponding interface. Classes may implement more than one interface if desired by separating each interface with a comma. For example : @@ -329,6 +330,22 @@ Methods to implement : } ``` +### MarkdownExtensionRecipe +Interface : `UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MarkdownExtensionRecipe` + +Methods to implement : +- `getMarkdownExtensions` : Return a list of custom [CommonMark](https://commonmark.thephpleague.com/) extension classes + + **Example:** + ```php + public function getMarkdownExtensions(): array + { + return [ + MyCustomMarkdownExtension::class, + ]; + } + ``` + ## Removing default sprinkles A default install, from the Skeleton, enables every [default sprinkle](/structure/sprinkles#bundled-sprinkles). But your app may not require every feature provided by these default sprinkles. For example, you might not need the Admin sprinkle if you don't need any user management features. diff --git a/app/pages/6.0/05.sprinkles/04.customize/docs.md b/app/pages/6.0/05.sprinkles/04.customize/docs.md index 3eef9e08..d05b48f1 100644 --- a/app/pages/6.0/05.sprinkles/04.customize/docs.md +++ b/app/pages/6.0/05.sprinkles/04.customize/docs.md @@ -142,7 +142,7 @@ $ composer update ``` > [!NOTE] -> If after running these steps, UserFrosting fails to find new classes that you add to `src/`, make sure that that the user running Composer had read permissions for your sprinkle. You can check that the path to your sprinkle's `src/` directory was actually added in `app/vendor/composer/autoload_psr4.php` You can also try running Composer with the `-vvv` flag for more detailed reporting. +> If after running these steps, UserFrosting fails to find new classes that you add to `src/`, make sure that that the user running Composer had read permissions for your sprinkle. You can check that the path to your sprinkle's `src/` directory was actually added in `vendor/composer/autoload_psr4.php` You can also try running Composer with the `-vvv` flag for more detailed reporting. ### The recipe diff --git a/app/pages/6.0/06.dependency-injection/02.the-di-container/docs.md b/app/pages/6.0/06.dependency-injection/02.the-di-container/docs.md index c7b0425f..5ce108ec 100644 --- a/app/pages/6.0/06.dependency-injection/02.the-di-container/docs.md +++ b/app/pages/6.0/06.dependency-injection/02.the-di-container/docs.md @@ -37,7 +37,7 @@ This is where the **dependency injection container (DIC)** comes into play. The > > You don't need a container to do dependency injection. However, a container can make injections easier. -UserFrosting uses [_PHP-DI 7_](https://php-di.org) as it's DIC implementation since it provides many powerful features that we rely on: +UserFrosting uses [_PHP-DI 7_](https://php-di.org) as its DIC implementation since it provides many powerful features that we rely on: 1. It creates dependencies lazily ("on demand"). Any service (and its dependencies) won't be created until the first time we access them. 2. Once an object has been created in the container, the same object is returned in each subsequent call to the container. diff --git a/app/pages/6.0/06.dependency-injection/03.default-services/docs.md b/app/pages/6.0/06.dependency-injection/03.default-services/docs.md index 92fbbc01..87fb9fcb 100644 --- a/app/pages/6.0/06.dependency-injection/03.default-services/docs.md +++ b/app/pages/6.0/06.dependency-injection/03.default-services/docs.md @@ -19,7 +19,7 @@ This service handles the [alert message stream](/advanced/alert-stream), sometim ### `Illuminate\Cache\Repository as Cache` -Creates an instance of a Laravel [Cache](https://laravel.com/docs/8.x/cache). See [Chapter 18](/advanced/caching) for more information. +Creates an instance of a Laravel [Cache](https://laravel.com/docs/cache). See [Chapter 18](/advanced/caching) for more information. ### `UserFrosting\Config\Config` @@ -77,7 +77,7 @@ See [Chapter 7](/routes-and-controllers) for more information about defining rou ### `UserFrosting\Session\Session` -Sets up UserFrosting's `Session` object, which serves as a wrapper for the `$_SESSION` superglobal. `Session` will use file- or database-based storage for sessions, depending on your configuration setting for `session.handler`. Session handlers are provided by [Laravel's session handlers](https://laravel.com/docs/8.x/session#configuration), which implement PHP's [`SessionHandlerInterface`](http://php.net/SessionHandlerInterface). +Sets up UserFrosting's `Session` object, which serves as a wrapper for the `$_SESSION` superglobal. `Session` will use file- or database-based storage for sessions, depending on your configuration setting for `session.handler`. Session handlers are provided by [Laravel's session handlers](https://laravel.com/docs/session#configuration), which implement PHP's [`SessionHandlerInterface`](http://php.net/SessionHandlerInterface). Please note that when using file-based sessions, UserFrosting places sessions in its own `/app/sessions` directory instead of PHP's default session directory. diff --git a/app/pages/6.0/06.dependency-injection/04.adding-services/docs.md b/app/pages/6.0/06.dependency-injection/04.adding-services/docs.md index 8b1fadaf..70642544 100644 --- a/app/pages/6.0/06.dependency-injection/04.adding-services/docs.md +++ b/app/pages/6.0/06.dependency-injection/04.adding-services/docs.md @@ -75,7 +75,7 @@ MapBuilder::class => function (Config $config) { ### Register your service -The next step is to tell UserFrosting to load your service in your [Sprinkle Recipe](/sprinkles/recipe#getservices). To do so, you only need to list all the service providers you want to automatically register inside the `$getServices` property of your sprinkle class: +The next step is to tell UserFrosting to load your service in your [Sprinkle Recipe](/sprinkles/recipe#getservices). To do so, you only need to list all the service providers you want to automatically register inside the `getServices()` method of your sprinkle class: **app/src/MyApp.php** diff --git a/app/pages/6.0/07.routes-and-controllers/03.front-controller/docs.md b/app/pages/6.0/07.routes-and-controllers/03.front-controller/docs.md index f978e719..4078377f 100644 --- a/app/pages/6.0/07.routes-and-controllers/03.front-controller/docs.md +++ b/app/pages/6.0/07.routes-and-controllers/03.front-controller/docs.md @@ -10,7 +10,7 @@ Sprinkles define their routes in classes and register them in their Recipe. Ther The following is an example of a `GET` route: ```php -$app->get('/api/users/u/{username}', function (string $username, Request $request, Response $response, array $args) +$app->get('/api/users/u/{username}', function (string $username, Request $request, Response $response) { $getParams = $request->getQueryParams(); @@ -20,8 +20,10 @@ $app->get('/api/users/u/{username}', function (string $username, Request $reques $payload = json_encode($result, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); $response->getBody()->write($payload); } else { - return $response->getBody()->write("No format specified"); + $response->getBody()->write("No format specified"); } + + return $response; }); ``` diff --git a/app/pages/6.0/07.routes-and-controllers/04.controller-classes/docs.md b/app/pages/6.0/07.routes-and-controllers/04.controller-classes/docs.md index a1d792cf..eb212b5d 100644 --- a/app/pages/6.0/07.routes-and-controllers/04.controller-classes/docs.md +++ b/app/pages/6.0/07.routes-and-controllers/04.controller-classes/docs.md @@ -35,8 +35,10 @@ class OwlController $payload = json_encode($result, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); $response->getBody()->write($payload); } else { - return $response->getBody()->write("No format specified"); + $response->getBody()->write("No format specified"); } + + return $response; } } ``` @@ -100,6 +102,7 @@ namespace UserFrosting\Sprinkle\Site\Controller; use UserFrosting\Sprinkle\Site\Model\Owl; use UserFrosting\Sprinkle\Site\Finder\VoleFinder; +use Slim\Views\Twig; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; diff --git a/app/pages/6.0/07.routes-and-controllers/05.registering-routes/docs.md b/app/pages/6.0/07.routes-and-controllers/05.registering-routes/docs.md index ba200dc4..30a7b6e7 100644 --- a/app/pages/6.0/07.routes-and-controllers/05.registering-routes/docs.md +++ b/app/pages/6.0/07.routes-and-controllers/05.registering-routes/docs.md @@ -6,7 +6,7 @@ description: Once your routes definitions are ready, you have to register them i So far we've seen how to [create route definitions](/routes-and-controllers/front-controller) and [controller classes](/routes-and-controllers/controller-classes). However, there one last step required for our routes to be enabled inside our application. That is registering the route class inside the [Sprinkle Recipe](/sprinkles/recipe#routes). > [!NOTE] -> Previous versions of UserFrosting relied on a naming convention for registering routes. Routes were expected to be placed in a special directory, and would automatically be registered at runtime. To provide more flexibility, the naming convention has been dropped in UserFrosting 5. You now have to register every class you wish to register, in the order you want them to be registered, inside the Sprinkle Recipe. +> Previous versions of UserFrosting relied on a naming convention for registering routes. Routes were expected to be placed in a special directory, and would automatically be registered at runtime. To provide more flexibility, the naming convention has been dropped in a previous version of UserFrosting. You now have to register every class you wish to register, in the order you want them to be registered, inside the Sprinkle Recipe. The first step is to create a new class that will return the Slim route definition. This class **must** implement the `UserFrosting\Routes\RouteDefinitionInterface` interface from the UserFrosting Framework. For example : @@ -51,7 +51,7 @@ class MyApp implements SprinkleRecipe { /** * Returns a list of routes definition in PHP files. * - * @return string[] + * @return class-string[] */ public function getRoutes(): array { @@ -79,7 +79,6 @@ Cannot register two routes matching "/" for method "GET" ``` To solve this, it's possible to manually customize a dependent Sprinkle Recipe. Check out the [Advanced Dev Features](/advanced) chapter for more info on this technique. - Another workaround is to [override](/advanced/custom-models#overwriting-existing-map) the Action class called in the dependent Sprinkle's route. diff --git a/app/pages/6.0/07.routes-and-controllers/06.client-input/01.validation/docs.md b/app/pages/6.0/07.routes-and-controllers/06.client-input/01.validation/docs.md index e9ae3bd4..21cf499d 100644 --- a/app/pages/6.0/07.routes-and-controllers/06.client-input/01.validation/docs.md +++ b/app/pages/6.0/07.routes-and-controllers/06.client-input/01.validation/docs.md @@ -176,7 +176,7 @@ if (count($errors) !== 0) { } ``` -The `validate` method will return `false` if any fields fail any of their validation rules. Notice that we throw an exception to handle any error messages that we wish to display to the client, and stop the execution of the controller code. +The `validate` method will return an array of error messages (keyed by field name) if any fields fail their validation rules, or an empty array if all fields are valid. Notice that we throw an exception to handle any error messages that we wish to display to the client, and stop the execution of the controller code. > [!IMPORTANT] > Internally, UserFrosting uses the [Valitron](https://github.com/vlucas/valitron) validation package to perform server-side validation. diff --git a/app/pages/6.0/07.routes-and-controllers/06.client-input/04.ajax/docs.md b/app/pages/6.0/07.routes-and-controllers/06.client-input/04.ajax/docs.md index 4ddbaab9..a0c41be4 100644 --- a/app/pages/6.0/07.routes-and-controllers/06.client-input/04.ajax/docs.md +++ b/app/pages/6.0/07.routes-and-controllers/06.client-input/04.ajax/docs.md @@ -29,9 +29,9 @@ use UserFrosting\Sprinkle\Core\Exceptions\NotFoundException; class UserController { - public function getUser(Request $request, Response $response, array $args): Response + public function getUser(int $id, Request $request, Response $response): Response { - $userId = $args['id']; + $userId = $id; // Fetch user from database $user = User::find($userId); diff --git a/app/pages/6.0/08.pages-and-layout/03.sprinkle-templates/docs.md b/app/pages/6.0/08.pages-and-layout/03.sprinkle-templates/docs.md index 51a39401..0e09d01e 100644 --- a/app/pages/6.0/08.pages-and-layout/03.sprinkle-templates/docs.md +++ b/app/pages/6.0/08.pages-and-layout/03.sprinkle-templates/docs.md @@ -138,7 +138,7 @@ Sometimes, we want to reuse a snippet across multiple different templates - for ... ``` -The last line `{% include "pages/partials/favicons.html.twig" %}` tells Twig to insert the contents of the `pages/partials/favicons.html.twig` template. Additional parameters can be passed to `include`, which will override any variables of the same name that were passed to the main template: +The last line `{% include "content/favicons.html.twig" %}` tells Twig to insert the contents of the `content/favicons.html.twig` template. Additional parameters can be passed to `include`, which will override any variables of the same name that were passed to the main template: ```twig
diff --git a/app/pages/6.0/08.pages-and-layout/04.filters-and-functions/docs.md b/app/pages/6.0/08.pages-and-layout/04.filters-and-functions/docs.md index 032f00e3..c6cf562b 100644 --- a/app/pages/6.0/08.pages-and-layout/04.filters-and-functions/docs.md +++ b/app/pages/6.0/08.pages-and-layout/04.filters-and-functions/docs.md @@ -16,12 +16,15 @@ You can access any [configuration value](/configuration/config-files) directly i ### checkAccess +> [!NOTE] +> `checkAccess` and `current_user` are provided by the **Account sprinkle**. They are not available unless your application depends on `UserFrosting\Sprinkle\Account\Account`. + You can perform permission checks in your Twig templates using the `checkAccess` helper function. This is useful when you want to render a portion of a page's content conditioned on whether or not a user has a certain permission. For example, this can be used to hide a navigation menu item for pages that the current user does not have access to: ```twig {% if checkAccess('uri_users') %}
  • - {{ translate("USER", 2) }} + {{ translate("USER", 2) }}
  • {% endif %} ``` @@ -252,7 +255,7 @@ use UserFrosting\Sprinkle\Core\Sprinkle\Recipe\TwigExtensionRecipe; // <-- Add t use UserFrosting\Sprinkle\Site\Twig\Extension; // <-- Add this // ... -class Core implements +class MyApp implements SprinkleRecipe, TwigExtensionRecipe, // <-- Add this { diff --git a/app/pages/6.0/09.configuration/02.config-files/docs.md b/app/pages/6.0/09.configuration/02.config-files/docs.md index 933647b5..5aaf0429 100644 --- a/app/pages/6.0/09.configuration/02.config-files/docs.md +++ b/app/pages/6.0/09.configuration/02.config-files/docs.md @@ -46,11 +46,13 @@ And I load it after the `mysite` Sprinkle, the resulting configuration array cre ```php [ - 'timezone' => 'America/New_York', 'site' => [ 'title' => 'Save the Kakapo', 'author' => 'David Attenborough', 'twitter' => '@savethekakapo' + ], + 'php' => [ + 'timezone' => 'America/New_York' ] ] ``` @@ -94,11 +96,13 @@ To access values from the final, merged configuration array during runtime, use ```php [ - 'timezone' => 'America/New_York', 'site' => [ 'title' => 'Save the Kakapo', 'author' => 'David Attenborough', 'twitter' => '@savethekakapo' + ], + 'php' => [ + 'timezone' => 'America/New_York' ] ] ``` diff --git a/app/pages/6.0/10.users/01.user-accounts/docs.md b/app/pages/6.0/10.users/01.user-accounts/docs.md index 6f7d0728..ac336855 100644 --- a/app/pages/6.0/10.users/01.user-accounts/docs.md +++ b/app/pages/6.0/10.users/01.user-accounts/docs.md @@ -175,7 +175,7 @@ $this->logger->info("User {$currentUser->user_name} updated their profile settin ]); ``` -Every logged event includes the user's id, IP address, timestamp, an event type (e.g., `update_profile_settings`), and a description of the event. You may add additional logging directly in your controllers, or you can attach them to Laravel [model events](https://laravel.com/docs/11.x/eloquent#events) so that they occur automatically when the model is created/saved/updated/deleted. +Every logged event includes the user's id, IP address, timestamp, an event type (e.g., `update_profile_settings`), and a description of the event. You may add additional logging directly in your controllers, or you can attach them to Laravel [model events](https://laravel.com/docs/eloquent#events) so that they occur automatically when the model is created/saved/updated/deleted. ## Account administration @@ -223,7 +223,7 @@ The administrator can later re-enable the account, if desired. User accounts can be deleted from the user profile page, or the user dropdown menu in the users table. -Deleting user accounts presents a problem because the user may have related data in the database that would become orphaned, potentially breaking other functionality in your site. For this reason, UserFrosting performs [soft deletes](https://laravel.com/docs/11.x/eloquent#soft-deleting) by default. The user record is not actually deleted, but instead a `deleted_at` timestamp is added to the record and the user is no longer able to sign in. Deleted users are also excluded from all queries unless the `withTrashed` method is added to the Eloquent query. Related entities (activities, roles, etc) are left alone. +Deleting user accounts presents a problem because the user may have related data in the database that would become orphaned, potentially breaking other functionality in your site. For this reason, UserFrosting performs [soft deletes](https://laravel.com/docs/eloquent#soft-deleting) by default. The user record is not actually deleted, but instead a `deleted_at` timestamp is added to the record and the user is no longer able to sign in. Deleted users are also excluded from all queries unless the `withTrashed` method is added to the Eloquent query. Related entities (activities, roles, etc) are left alone. If you really want to completely remove the user from the database, you can call the `User::forceDelete` method in your controller logic. diff --git a/app/pages/6.0/12.mail/01.the-mailer-service/docs.md b/app/pages/6.0/12.mail/01.the-mailer-service/docs.md index 2ccfaef3..0afa4540 100644 --- a/app/pages/6.0/12.mail/01.the-mailer-service/docs.md +++ b/app/pages/6.0/12.mail/01.the-mailer-service/docs.md @@ -170,7 +170,7 @@ $this->mailer->sendDistinct($message); `sendDistinct` will send a **separate** email to each recipient, passing in any custom data that was defined in the `EmailRecipient` constructor. If you are trying to send a single message to a list of recipients, just use `send` instead. Note that in the case of `send`, any recipient-specific Twig parameters will be ignored. > [!NOTE] -> By default, `sendDistinct` and `send` will clear the list of recipients from your message object after successfully sending. To prevent this from happening (for example, if you want to send the message again), you can set the second parameter of either of these methods to `true`. +> By default, `sendDistinct` and `send` will clear the list of recipients from your message object after successfully sending. To prevent this from happening (for example, if you want to send the message again), you can set the second parameter of either of these methods to `false`. ### Error handling diff --git a/app/pages/6.0/13.i18n/01.introduction/docs.md b/app/pages/6.0/13.i18n/01.introduction/docs.md index a33c1296..39d45af0 100644 --- a/app/pages/6.0/13.i18n/01.introduction/docs.md +++ b/app/pages/6.0/13.i18n/01.introduction/docs.md @@ -23,7 +23,7 @@ Finaly, it can also makes it easier for non-coders to review your content. Wheth To better understand how to support multiple locales within your site, we need to start by looking at how UserFrosting handles translation of text. -UserFrosting [i18n module](https://github.com/userfrosting/framework/tree/main/packages/framework/src/I18n) is composed of three objects that work together to handle translation duties. Those objects are: +UserFrosting [i18n module](https://github.com/userfrosting/monorepo/tree/main/packages/framework/src/I18n) is composed of three objects that work together to handle translation duties. Those objects are: 1. Locale 2. Dictionary @@ -53,7 +53,7 @@ return [ ]; ``` -This information is stored in **languages files**. These are normal PHP files typically located in `app/sprinkles/{sprinkleName}/locale/{locale}/messages.php` and grouped into folders named after the locale code, as pictured below. Each locale can have as many files as needed (eg. `messages.php`, `foo.php`, `bar.php`, etc.) for easier maintenance. Those files will be merged together at runtime to create a **compiled dictionary** of all the keys available for the translator to use. +This information is stored in **languages files**. These are normal PHP files typically located in `app/locale/{locale}/messages.php` within the sprinkle package and grouped into folders named after the locale code, as pictured below. Each locale can have as many files as needed (eg. `messages.php`, `foo.php`, `bar.php`, etc.) for easier maintenance. Those files will be merged together at runtime to create a **compiled dictionary** of all the keys available for the translator to use. **locale/es_ES/example.php** @@ -74,7 +74,7 @@ return [ ]; ``` -Some locales have a parent locale, and each locale's language files will be loaded on top of the parent's one. So for example, since the Spanish version above doesn't have any value for the `ACCOUNT_SPECIFY_AGE` key, the English value would be returned for that key if `en_ES` has `en_US` for parent. +Some locales have a parent locale, and each locale's language files will be loaded on top of the parent's one. So for example, since the Spanish version above doesn't have any value for the `ACCOUNT_SPECIFY_AGE` key, the English value would be returned for that key if `es_ES` has `en_US` for parent. > [!TIP] > Bakery provides locale management commands: `locale:info` lists all available locales, `locale:dictionary` displays the compiled dictionary for a locale, and `locale:compare` compares two locales to find missing keys and untranslated values. See the [Built-in Commands](/cli/commands) page for complete documentation. @@ -83,7 +83,7 @@ Just like [routes](/routes-and-controllers/front-controller), the names of the f For example, if you want your sprinkle to overwrite a value in the core, you can redefine the same key in your sprinkle : -**app/sprinkles/core/locale/en_US/example.php** +**app/locale/en_US/example.php** ```php return[ @@ -93,7 +93,7 @@ return[ ]; ``` -**app/sprinkles/MySite/locale/en_US/MyCustomLanguage.php** +**app/locale/en_US/MyCustomLanguage.php** ```php return [ @@ -104,7 +104,7 @@ return [ When processed using the English locale, `ACCOUNT_SPECIFY_USERNAME` will be linked to the phrase `Enter your name!`. > [!NOTE] -> After updating your language files, you need to clear the dictionary cache using the ```php bakery clear-cache``` command from the CLI for the changes to be visible. +> After updating your language files, you need to clear the dictionary cache using the `php bakery clear-cache` command from the CLI for the changes to be visible. ### The Translator diff --git a/app/pages/6.0/13.i18n/03.translator/docs.md b/app/pages/6.0/13.i18n/03.translator/docs.md index 4bb95636..e4fb31ad 100644 --- a/app/pages/6.0/13.i18n/03.translator/docs.md +++ b/app/pages/6.0/13.i18n/03.translator/docs.md @@ -27,13 +27,13 @@ echo $this->translator->translate("ACCOUNT_SPECIFY_USERNAME"); The current locale will be automatically defined and the associated dictionary automatically loaded by UserFrosting. > [!TIP] -> The translator service contains others public methods that can be useful for you. For example, you can use it to retrieve the associated dictionary and locale. See the [i18n API documentation](https://github.com/userfrosting/framework/tree/main/packages/framework/src/I18n) for more information. +> The translator service contains others public methods that can be useful for you. For example, you can use it to retrieve the associated dictionary and locale. See the [i18n API documentation](https://github.com/userfrosting/monorepo/tree/main/packages/framework/src/I18n) for more information. ### In Twig The translator service is also available as a [Twig function](/pages-and-layout/filters-and-functions). Placeholders can be passed to the Twig function too: -``` +```twig {{ translate("ACCOUNT_SPECIFY_USERNAME") }} ``` @@ -101,6 +101,10 @@ See the [Luxon documentation](https://moment.github.io/luxon/#/formatting) for m The translator functions are also available as global properties in all Vue templates. The `$t` function is an alias for `translate()`, and `$tdate` is an alias for `translateDate()`: ```vue + +