diff --git a/docs/6.x/extend/approach.md b/docs/6.x/extend/approach.md index a4d9f3f3d..df8f4cfbe 100644 --- a/docs/6.x/extend/approach.md +++ b/docs/6.x/extend/approach.md @@ -15,7 +15,7 @@ Some low-level plugins may no longer be necessary in the Laravel ecosystem, but Examples of this are: - **Mail transport adapters** — Projects can set up mailers via `config/mail.php` and select one in . -- **Custom log back-ends** — As with prior versions of Craft, plugins may not be initialized early enough in the app’s lifecycle to capture a complete picture. Consider whether projects can effectively use Laravel’s built-in [logging](laravellogging) tools. +- **Custom log back-ends** — As with prior versions of Craft, plugins may not be initialized early enough in the app’s lifecycle to capture a complete picture. Consider whether projects can effectively use Laravel’s built-in [logging](laravel:logging) tools. ::: warning Filesystems have been completely removed in Craft 6.x. diff --git a/docs/6.x/extend/commands.md b/docs/6.x/extend/commands.md index ef6ec06e8..f54e91056 100644 --- a/docs/6.x/extend/commands.md +++ b/docs/6.x/extend/commands.md @@ -45,7 +45,7 @@ class GenerateReport extends Command } ``` -Your command’s [arguments and options](artisan#defining-input-expectations) are determined by its _signature_. +Your command’s [arguments and options](laravel:artisan#defining-input-expectations) are determined by its _signature_. Laravel takes care of matching command arguments with those of the `handle()` method; other arguments are resolved with dependency injection. In this example, `$manager` isn’t an argument that we’d expect a user to supply—it’s just a [service](services.md) we want access to in that scope. @@ -59,7 +59,7 @@ public array $commands = [ ## Command Closure -You can also create simple commands directly in your plugin’s `boot()` method, using a [laravel:artisan#closure-commands]: +You can also create simple commands directly in your plugin’s `boot()` method, using a [closure command](laravel:artisan#closure-commands): ```php Artisan::command('report:generate {templateId}', function (Manager $manager, int $templateId) { diff --git a/docs/6.x/extend/config.md b/docs/6.x/extend/config.md index fbbb51fcd..de10099ee 100644 --- a/docs/6.x/extend/config.md +++ b/docs/6.x/extend/config.md @@ -134,7 +134,7 @@ public function rulesClass(): string ``` You may also tag the settings model with the `#[Ruleset(MyPluginSettingsRuleset::class)]` attribute. -For more control over how your settings data is normalized, return your own [form request](validation#form-request-validation) class from `getSettingsRequestClass()`. +For more control over how your settings data is normalized, return your own [form request](validation.md#form-request-validation) class from `getSettingsRequestClass()`. When using a custom `FormRequest`, your rules are evaluated against the entire request, not just the nested `settings` key that Craft ordinarily assigns to your settings model. The example above (validating `adminNotificationsAddress` on the settings model) would need to be updated to `settings.adminNotificationsAddress` if returned from `FormRequest::rules()`. diff --git a/docs/6.x/extend/http.md b/docs/6.x/extend/http.md index 6473072d4..3b9192b4d 100644 --- a/docs/6.x/extend/http.md +++ b/docs/6.x/extend/http.md @@ -80,6 +80,27 @@ Laravel’s “terminable” middleware is not compatible with all hosting envir app()->terminating($this->flushLazyEvents()); ``` +### CSRF + +Laravel verifies CSRF tokens on web routes, so a form posted without a token is rejected with a 419. +Front-end templates can keep using `{{ csrfInput() }}`, and Ajax requests can send the `XSRF-TOKEN` cookie value in an `X-XSRF-TOKEN` header. + +If an endpoint must accept posts without a token (a webhook, or a plugin that used to disable `enableCsrfValidation` on a Yii controller), exempt its URI from your plugin’s `boot()` method: + +```php +use CraftCms\Cms\Route\Routes; +use Illuminate\Foundation\Http\Middleware\PreventRequestForgery; + +public function boot(Routes $routes): void +{ + PreventRequestForgery::except([ + $routes->actionTriggerUriPrefix()."/$this->handle/save", + ]); +} +``` + +Craft uses the same mechanism for its own `graphql/api` action. + ### Authorization Controllers in Laravel don’t often do their own authorization, because they’re no longer automatically exposed to the router. @@ -209,7 +230,7 @@ In some situations, you may still need to resolve permissions directly via a use ```php // Check against the current user: -Auth::craftUser()->can('triggerReport'); +currentUserElement()->can('triggerReport'); // Check against a specific user: $recipient->can('requestAccess', $report); diff --git a/docs/6.x/extend/local-dev.md b/docs/6.x/extend/local-dev.md index 16cc33848..6701d5ba5 100644 --- a/docs/6.x/extend/local-dev.md +++ b/docs/6.x/extend/local-dev.md @@ -6,7 +6,7 @@ If the host project doesn’t include the recommended `scripts` for a Laravel ap ```bash ddev composer require myorg/activity-plugin -# New: Ask Laravel to +# New: Ask Laravel to discover the package ddev artisan package:discover ``` diff --git a/docs/6.x/extend/provider.md b/docs/6.x/extend/provider.md index b0a812e83..b17ec49e6 100644 --- a/docs/6.x/extend/provider.md +++ b/docs/6.x/extend/provider.md @@ -7,6 +7,13 @@ Plugins now extend `CraftCms\Cms\Plugin\Plugin`, and are Laravel [service provid Many plugins will only need to update their [settings model](#settings) and rename the `init()` method to `boot()`. At this point—with the adapter installed—your plugin should be in a stable state from which you can gradually move [initialization logic](avenues.md#initialization) to the new architecture. +::: warning +The adapter only routes `actions/{handle}/…` URLs to plugins that extend the legacy `craft\base\Plugin` class. +Once your plugin extends `CraftCms\Cms\Plugin\Plugin`, any Yii controllers it ships return a 404 until you port them to Laravel controllers and [route](http.md#action-paths) them in `routes/actions.php`. +Switch the base class together with your controllers, and test your action URLs afterwards. +The settings page also changes: `settingsHtml()` is not used by the new base class, so implement `settingsForm()` (see [settings](config.md#settings-form)). +::: + You are not obligated to adopt `PascalCase` namespaces, but if you do (and your base class is named something other than `Plugin`, in your autoloading root), make the corresponding update to the `extra.class` property in `composer.json`: ```json{8} @@ -27,6 +34,15 @@ In order to support the Craft plugin lifecycle (installation and uninstallation, ## Settings The `createSettings()` method replaces `createSettingsModel()`, and must return a subclass of `CraftCms\Cms\Plugin\PluginSettings` (or `null`). +Note that it is a **static** method: + +```php +protected static function createSettings(): ?PluginSettings +{ + return new Settings(); +} +``` + This base class retains much of the same [component](models.md#data-objects) functionality, and should be a drop-in replacement for `craft\base\Model`. ::: tip diff --git a/docs/6.x/extend/session.md b/docs/6.x/extend/session.md index b14ec49f8..8f9d1958b 100644 --- a/docs/6.x/extend/session.md +++ b/docs/6.x/extend/session.md @@ -4,7 +4,7 @@ The session is deeply integrated with validation, templating, controllers, and a ## Auth -You can always get the current `User` element with `Illuminate\Support\Facades\Auth::craftUser()`. +You can always get the current user with the `craftUser()` method on the request (`$request->craftUser()`), or the `CraftCms\Cms\currentUser()` and `CraftCms\Cms\currentUserElement()` helper functions. Users are largely unchanged, but the class has been relocated to `CraftCms\Cms\User\Elements\User` and some of its functionality has been split out into concerns (or is handled entirely by Laravel). ## Flashes diff --git a/docs/6.x/extend/structure.md b/docs/6.x/extend/structure.md index 3a400bde4..8ab17a773 100644 --- a/docs/6.x/extend/structure.md +++ b/docs/6.x/extend/structure.md @@ -9,6 +9,31 @@ Within your plugin’s `src/` directory, you are not obligated to use any partic While some conventions are similar, Laravel’s opinionated paths (i.e. `Listeners/` for auto-registered [event](events.md) handlers) are only relevant in the project’s space (`app/`), and have no bearing on plugins. ::: +## Class Organization + +You are free to organize `src/` however you like, but we recommend following the conventions that Craft’s own code (and most Laravel packages) use: a PascalCase PSR-4 namespace (like `CraftCms\MyPlugin`), with classes grouped by what they _are_. +Craft groups by feature first, and by role within each feature. + +| Kind of class | Location | Core example | +| --- | --- | --- | +| Plugin class and settings | `src/Plugin.php`, `src/Settings.php` | | +| Controllers | `src/Http/Controllers/` | `CraftCms\Cms\Http\Controllers\AddressesController` | +| Form requests | `src/Http/Requests/` | | +| Middleware | `src/Http/Middleware/` | | +| Events | `src/Events/` | `CraftCms\Cms\Section\Events\SectionSaved` | +| Listeners | `src/Listeners/` | | +| Data objects | `src/Data/` | `CraftCms\Cms\Section\Data\Section` | +| Eloquent models (formerly records) | `src/Models/` | `CraftCms\Cms\Section\Models\Section` | +| Enums | `src/Enums/` | `CraftCms\Cms\Section\Enums\SectionType` | +| Exceptions | `src/Exceptions/` | `CraftCms\Cms\Section\Exceptions\SectionNotFoundException` | +| Jobs, commands, policies | `src/Jobs/`, `src/Console/Commands/`, `src/Policies/` | | +| Services | A feature folder, with `#[Singleton]` classes | `CraftCms\Cms\Section\Sections` | + +Larger plugins can nest these inside a feature folder (`src/Reporting/Events/`, `src/Reporting/Data/`). + +When porting a Craft 5 plugin, this usually means moving classes out of Yii-style lowercase folders (`controllers/`, `models/`, `events/`, `services/`) as you port them. +Directory names are case-sensitive, so make sure each namespace matches its path exactly, and list every renamed class in your upgrade notes. + ## Plugin Base Class Craft needs to be able to locate your base class. diff --git a/docs/6.x/extend/upgrade.md b/docs/6.x/extend/upgrade.md index 1b5939b8e..2aee5d505 100644 --- a/docs/6.x/extend/upgrade.md +++ b/docs/6.x/extend/upgrade.md @@ -26,6 +26,12 @@ The legacy base plugin class (`craft\base\Plugin`) and the interfaces it impleme We have mainly observed this with the `attributes()` and `attributeLabels()` methods. If you don’t use these, there’s nothing to do here. +::: tip +This applies while you keep extending `craft\base\Plugin` and `craft\base\Model`. +The new `CraftCms\Cms\Plugin\PluginSettings` class extends `CraftCms\Cms\Component\Component`, which has no `attributes()` method, so a settings class that moves to it can’t call `parent::attributes()`. +Expose values as public properties instead. +::: + Basically, _yes_!