### Install Craft Cloud Extension with Composer Source: https://craftcms.com/docs/cloud/extension Alternatively, install the craftcms/cloud package directly using Composer. After installation, run the setup command to configure the extension. ```bash composer require craftcms/cloud php craft cloud/setup ``` -------------------------------- ### Install Package and Discover Packages Source: https://craftcms.com/docs/6.x/extend/local-dev Use these commands to install a new package and then ask Laravel to discover it, especially if the host project lacks recommended scripts. ```bash ddev composer require myorg/activity-plugin # New: Ask Laravel to ddev artisan package:discover ``` -------------------------------- ### Example Preview Domain Source: https://craftcms.com/docs/cloud/domains This is an example of a preview domain format for a Craft Cloud environment. Preview domains are automatically generated and end with 'preview.craft.cloud'. ```text project-my-handle-environment-b62dec18.preview.craft.cloud ``` -------------------------------- ### Install a Craft CMS Plugin Source: https://craftcms.com/docs/6.x/extend/local-dev Use this command to install a plugin via the Craft CMS console. Legacy options are also provided. ```bash ddev artisan craft:plugin:install # ...or either of these legacy options: ddev php craft plugin:install ddev php craft plugin/install ``` -------------------------------- ### Launch Craft CMS Project Source: https://craftcms.com/docs/getting-started-tutorial/install Open your installed Craft CMS project in a web browser using DDEV. ```bash ddev launch ``` -------------------------------- ### Run the 'whoami' command Source: https://craftcms.com/docs/getting-started-tutorial/environment/terminal Executes the 'whoami' command to display your current username. The line starting with '#' shows an example of the expected output. ```bash whoami # -> oli ``` -------------------------------- ### Install http-message-sig for Node.js Source: https://craftcms.com/docs/cloud/request-signing Install the http-message-sig package to generate RFC 9421-compliant signatures. ```bash npm install http-message-sig ``` -------------------------------- ### Install Craft Cloud Extension Source: https://craftcms.com/docs/cloud/extension Install the Craft Cloud extension using the provided command. This command is available for Craft CMS 4.5.10 and later. ```bash php craft setup/cloud ``` -------------------------------- ### Require the Adapter Package Source: https://craftcms.com/docs/6.x/extend/adapter.html Install the adapter package using Composer to begin porting your plugin or compatibility release. ```bash composer require craftcms/yii2-adapter ``` -------------------------------- ### Create a New Table Source: https://craftcms.com/docs/6.x/extend/migrations.html Example of using the `Schema` facade and `Blueprint` builder to create a new database table with an ID, name, and handle column. ```php // Create a new table: Schema::create('my_table', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('handle'); }); ``` -------------------------------- ### Set up a New Craft Project for Cloud Source: https://craftcms.com/docs/cloud/getting-started Run this command to install the necessary Craft Cloud extension and configuration for new sites. The project will continue to function locally. ```bash ddev craft setup/cloud ``` -------------------------------- ### Install CKEditor Plugin via Command Line Source: https://craftcms.com/docs/getting-started-tutorial/configure/resources After adding the package, install the CKEditor plugin using the Craft CMS console command. This is also run within the DDEV environment. ```bash ddev php craft plugin/install ckeditor ``` -------------------------------- ### Define Front-End Routes with Controllers Source: https://craftcms.com/docs/6.x/extend/http.html Example of a `web.php` file for defining front-end routes. This maps a POST request to a specific controller. ```php use MyOrg\Activity\Http\Controllers\TrackEvent; use Illuminate\Support\Facades\Route; Route::post('activity/track', TrackEvent::class); ``` -------------------------------- ### Vite Server Configuration for DDEV Source: https://craftcms.com/docs/6.x/extend/assets Example Vite server configuration for DDEV environments, specifying host, origin, port, and CORS settings to ensure compatibility with the development setup. ```javascript // ... const host = '0.0.0.0'; const port = 3000; const origin = `${process.env.DDEV_PRIMARY_URL}:${port}`; export default defineConfig({ server: { host, origin, port, cors: { // Live dangerously... origin: true, // ...or permit only DDEV origins. // See: https://dev.to/mandrasch/vite-is-suddenly-not-working-anymore-due-to-cors-error-ddev-3673 // origin: /https?:\/([A-Za-z0-9\-\.]+)?(\.ddev.site)(?::\d+)?$/, }, }, // ... }); ``` -------------------------------- ### Creating a Facade for a Service in Craft 6.x Source: https://craftcms.com/docs/6.x/extend/services.html Provides an example of creating a facade for a service in Craft 6.x. Facades offer a static-like interface to services, simplifying their access throughout the application. ```php namespace MyOrg\Activity\Facades; use Illuminate\Support\Facades\Facade; class Reports extends Facade { #[\]Override] protected static function getFacadeAccessor(): string { return \MyOrg\Activity\Reporting\Manager::class; } } # -> Reports::generate(...) ``` -------------------------------- ### Navigate directories Source: https://craftcms.com/docs/getting-started-tutorial/environment/terminal Use 'cd' followed by a path to change the working directory. Examples include moving up a directory ('cd ..'), navigating to the root, a specific path, or the home directory. ```bash cd .. pwd cd /Applications pwd cd ~/Desktop pwd cd ~ pwd ``` ```powershell cd .. cd cd C:\Windows cd cd %HOMEPATH%/Desktop cd cd %HOMEPATH% cd ``` -------------------------------- ### GraphQL Query Response Example Source: https://craftcms.com/docs/getting-started-tutorial/more/graphql.html This is a sample JSON response for the GraphQL query above, showing the structure and data returned for a blog entry and site description. ```json { "data": { "entry": { "title": "My Trip to Bend", "postDate": "15 Oct 2024", "postDateAlt": "2024-10-15", "url": "https://tutorial.ddev.site/blog/my-trip-to-bend", "featureImage": [ { "title": "Highway 26", "url": "https://tutorial.ddev.site/uploads/images/highway-26.JPG", "sized": "https://tutorial.ddev.site/uploads/_900x600_crop_center-center_90_none/5/highway-26.jpg" } ], "postContent": [ { "typeHandle": "text", "text": "Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Curabitur blandit tempus porttitor. Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas sed diam eget risus varius blandit sit amet non magna. Nullam quis risus eget urna mollis ornare vel eu leo." }, { "typeHandle": "image", "image": [ { "title": "Warm Springs", "url": "https://tutorial.ddev.site/uploads/images/warm-springs.JPG" } ] }, { "typeHandle": "text", "text": "Vestibulum id ligula porta felis euismod semper. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Nullam quis risus eget urna mollis ornare vel eu leo.\n\nCras mattis consectetur purus sit amet fermentum. Vestibulum id ligula porta felis euismod semper. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit." } ], "postCategories": [ { "title": "Trips", "url": "https://tutorial.ddev.site/blog/topic/trips" }, { "title": "Oregon", "url": "https://tutorial.ddev.site/blog/topic/oregon" } ] }, "globalSet": { "description": "

Photos and stories from around the Pacific Northwest.

\n" } } } ``` -------------------------------- ### Using a Facade to Fork a Template Source: https://craftcms.com/docs/6.x/extend/services.html This example shows how to use a facade (`Reports`) to call a method that likely involves dependency injection for its arguments, such as `$template` and `$currentUser`. ```php Reports::fork($template, $currentUser); ``` -------------------------------- ### Run Upgrade Tool in Docker Source: https://craftcms.com/docs/6.x/upgrade.html If composer packages cannot be installed directly, mount your project into a Docker container to run the upgrade tool commands. ```bash # On the host machine... docker run --interactive --tty --volume $PWD:/app composer bash # ...in the container: -> 42dae745a6ab:/app# composer global require craftcms/craft6-revamp -> ... -> 42dae745a6ab:/app# composer global exec craft6-revamp ``` -------------------------------- ### Extract Project ID from URL Source: https://craftcms.com/docs/cloud/billing This example shows how to identify a project's ID from its URL in Craft Console for support inquiries. ```text /accounts/{org-name}/cloud/projects/{project-id} ``` -------------------------------- ### Twig Syntax Example Source: https://craftcms.com/docs/getting-started-tutorial/build/twig.html This snippet demonstrates fundamental Twig syntax, including comments, variable output, setting variables, loops, and filters. It's useful for understanding basic Twig structure and functionality. ```twig {# This is a comment in Twig! It won’t appear in the HTML output. #} {# Here’s some text, dynamically inserted into an HTML tag: #}

{{ siteName }}

{# What about a dynamic value? #}

Today is {{ now | date }}!

{# This is a variable, and it’s being set to a list (or “array”) of colors: #} {% set colors = ['red', 'green', 'blue'] %} {# How many colors are in the list? #}

The following list contains {{ colors | length }} color(s).

{# This is a loop that outputs those items, sorted alphabetically: #} ``` -------------------------------- ### Require craftcms/yii2-adapter Source: https://craftcms.com/docs/6.x/extend/upgrade Install the Yii2 adapter package using Composer. This is a required step for plugins to initialize properly in a Laravel project with Craft 6.x. ```bash composer require craftcms/yii2-adapter ``` -------------------------------- ### Craft CMS Project File Structure Source: https://craftcms.com/docs/getting-started-tutorial/install/files Displays the default directory and file layout for a new Craft CMS installation. This structure should be maintained throughout the tutorial. ```bash craft ├── .ddev ├── config ├── storage ├── templates ├── vendor ├── web ├── .env ├── .env.example.dev ├── .env.example.production ├── .env.example.staging ├── .gitignore ├── bootstrap.php ├── composer.json ├── composer.lock └── craft ``` -------------------------------- ### Default Cache Rule Example Source: https://craftcms.com/docs/cloud/static-caching Represent the default caching behavior with a catch-all rule that excludes specific UTM parameters from the cache key for the homepage. ```yaml php-version: 8.5 cache: rules: - pattern: "/*?" query-string: mode: exclude keys: - utm_source - utm_medium - utm_campaign - # ... ``` -------------------------------- ### Binding Closure Listeners in bootPlugin() Source: https://craftcms.com/docs/6.x/extend/events Closures must be bound in your plugin’s `bootPlugin()` method. This example shows how to listen for a `RegisterFilesystemTypes` event and push a new filesystem type. ```php use CraftCms\Cms\Filesystem\Events\RegisterFilesystemTypes; use MyVendor\MyPlugin\Filesystems\Dropbox // ... public function bootPlugin(): void { // Closures must be bound in your plugin’s bootPlugin() method, as PHP doesn’t support them as static class properties! Event::listen(function(RegisterFilesystemTypes $event): void { $event->types->push(Dropbox::class) }); } ``` -------------------------------- ### Get Temporary Paths in Craft Cloud Source: https://craftcms.com/docs/cloud/plugin-development Use Craft's Path service to get temporary, storage, and cache directory paths. Avoid hard-coding or dynamically constructing these paths. ```php $path = Craft::$app->getPath(); $tmp = $path->getTempPath(); $storage = $path->getStoragePath(); $cache = $path->getCachePath(); // ... ``` -------------------------------- ### Enable Headless Mode in Craft CMS Source: https://craftcms.com/docs/getting-started-tutorial/more/graphql.html Add this configuration to `config/general.php` to enable headless mode. This optimizes the installation for content delivery and hides template settings. ```php headlessMode(true) ; ``` -------------------------------- ### Get the current working directory Source: https://craftcms.com/docs/getting-started-tutorial/environment/terminal Use 'pwd' on macOS/Linux or 'cd' on Windows to determine your current working directory. ```bash pwd ``` ```powershell cd ``` -------------------------------- ### Twig Function Example Source: https://craftcms.com/docs/getting-started-tutorial/build/twig.html Illustrates the use of a Twig function, such as `getenv`, which is responsible for fetching or generating a value. Functions often do not require input, unlike filters. ```twig getenv('ENV_VAR_NAME') ``` -------------------------------- ### Accessing and Displaying Overridden Settings Source: https://craftcms.com/docs/6.x/extend/config Retrieve all configuration overrides for a plugin and use this information to conditionally render form fields. This example shows how to identify overridden settings and display them as read-only. ```php #[\Override] protected function settingsHtml(): string { // Get the whole static config array (so `nulls` are significant): $overrides = config(sprintf('craft.%s', $this->handle)); return template('plugin-handle/_settings', [ 'settings' => $this->getSettings(), 'overrides' => array_keys($overrides), ]); } ``` ```twig {% set mySettingOverridden = 'mySetting' in overrides %} {{ forms.textField({ id: 'mySetting', label: 'My Setting'|t('plugin-handle'), static: mySettingOverridden, readonly: mySettingOverridden, warning: mySettingOverridden ? 'This setting is being overridden by `{name}` in `{file}`.'|t('plugin-handle', { name: 'mySetting', file: 'config/craft/my-plugin.php' }), }) }} ``` -------------------------------- ### Basic Vite Configuration Source: https://craftcms.com/docs/6.x/extend/assets A minimal `vite.config.js` setup for a Craft CMS plugin, matching the input and output paths defined in the plugin's PHP configuration. ```javascript import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/js/plugin.js', 'resources/css/plugin.css' ], publicDirectory: 'resources/dist', }), ], }); ``` -------------------------------- ### Headless CMS: craft-cloud.yaml with 'noop' npm-script Source: https://craftcms.com/docs/cloud/config Updates `craft-cloud.yaml` to use the 'noop' npm script, disabling the build process for headless or decoupled front-end setups. ```yaml # ... npm-script: 'noop' ``` -------------------------------- ### Gather data before rendering a template with Laravel routes Source: https://craftcms.com/docs/6.x/upgrade.html This Laravel route example demonstrates how to gather data before rendering a template, serving as an alternative to `Route::view()`. It uses a closure to process the request and pass data to the `pageTemplate` helper. ```php use Illuminate\Http\Request; use function CraftCms\Cms\pageTemplate; Route::get('newsletter', function (Request $request) { return pageTemplate('_forms/newsletter', [ 'source' => $request->input('campaign_id', 'internal'), ]); }); ``` -------------------------------- ### Create and Navigate to Project Folder Source: https://craftcms.com/docs/getting-started-tutorial/install Use these commands to create a new directory for your project and change into it. ```bash cd /path/to/dev/folder mkdir tutorial cd tutorial ``` -------------------------------- ### Install Craft CMS 6 Upgrade Tool Source: https://craftcms.com/docs/6.x/upgrade.html Install the global composer package for the Craft 6 revamp tool. This tool helps automate the upgrade process. ```bash composer global install craftcms/craft6-revamp ``` -------------------------------- ### Get Build Artifact URL Source: https://craftcms.com/docs/cloud/builds Use `craft\cloud\Helper::artifactUrl()` to get the build-specific URL for an artifact. This is useful when needing to access manifest files or other build outputs from the CDN. ```php craft\cloud\Helper::artifactUrl('path/to/manifest.json') ``` -------------------------------- ### Implement Custom Filesystem Class Source: https://craftcms.com/docs/6.x/extend/disks Extend the base Filesystem class and implement `getDiskConfig()` to define disk configuration for custom storage adapters like Backblaze B2. Ensure all required credentials and settings are parsed from environment variables. ```php use CraftCms\Cms\Filesystem\Filesystems\Filesystem; use CraftCms\Cms\Support\Env; class Backblaze extends Filesystem { // ... public function getDiskConfig(): array { return [ 'driver' => 'b2', 'bucketId' => Env::parse($this->bucketId), 'bucketName' => Env::parse($this->bucketName), 'accountId' => Env::parse($this->accountId), 'applicationKey' => Env::parse($this->applicationKey), 'url' => Env::parse($this->url), // ... ] } } ``` -------------------------------- ### Accessing Project Config via Facade Source: https://craftcms.com/docs/6.x/extend/services.html This demonstrates accessing the `ProjectConfig` service using its provided facade. Facades offer a convenient, static-like interface to services managed by the application container. ```php CraftCms\Cms\Support\Facades\ProjectConfig::get('graphql.enabled') ``` -------------------------------- ### Check DDEV Version Source: https://craftcms.com/docs/getting-started-tutorial/environment/stack Use this command to verify your installed DDEV version. Ensure you are running DDEV 1.23.4 or later. ```bash ddev -v # -> ddev version v1.23.4 ``` -------------------------------- ### Render a Template with `template()` Helper Source: https://craftcms.com/docs/6.x/extend/templates.html Use the `template()` helper for most situations to render a template. Pass the template path and an array of variables. ```php return template('my-plugin/_settings', [ 'settings' => $this->getSettings(), ]); ``` -------------------------------- ### Basic GraphQL Ping Query Source: https://craftcms.com/docs/getting-started-tutorial/more/graphql.html Run this query in the GraphiQL explorer to verify your GraphQL setup. It should return 'pong' if the connection is successful. ```graphql { ping } ``` -------------------------------- ### Redirecting with Flash Messages Source: https://craftcms.com/docs/6.x/extend/session Example of redirecting back to the previous page and flashing an error message, along with additional serializable data, to the session. ```php return back() ->with('error', t('This is already on your favorites list!')) ->with([ 'favorite' => CraftCms\Cms\Support\Arr::toArray($favorite), ]); ``` -------------------------------- ### Use Facade to Track Events Source: https://craftcms.com/docs/6.x/extend/helpers.html Once a facade is defined, it can be used as a shortcut to access the underlying service's methods. This provides a cleaner syntax compared to directly accessing the service container. ```php use MyOrg\Activity\Support\Events; Events::track('pageView', ['elementId' => $entry->id]); // This still works: app(\MyOrg\Activity\Reporting\Manager::class)->track( 'pageView', ['elementId' => $entry->id], ); ``` -------------------------------- ### List directory contents Source: https://craftcms.com/docs/getting-started-tutorial/environment/terminal Use 'ls' on macOS/Linux or 'dir' on Windows to view the files and folders in the current directory. ```bash ls ``` ```powershell dir ``` -------------------------------- ### Drop Table Prefix for Existing Projects Source: https://craftcms.com/docs/cloud/getting-started If your project uses the `tablePrefix` setting or `CRAFT_DB_TABLE_PREFIX` config variable, run this command before proceeding with Cloud setup. ```bash ddev craft db/drop-table-prefix ``` -------------------------------- ### Install CKEditor Plugin via Composer Source: https://craftcms.com/docs/getting-started-tutorial/configure/resources Use Composer to add the Craft CMS CKEditor plugin to your project. This command is run within the DDEV environment. ```bash ddev composer require craftcms/ckeditor -w ``` -------------------------------- ### Create a Static CSS File Source: https://craftcms.com/docs/getting-started-tutorial/build/routing.html Add a simple CSS file to the web root. This file will be served directly by the web server without involving Craft CMS. ```css /* Hello, world! */ ``` -------------------------------- ### Register Callback for App Booted Event Source: https://craftcms.com/docs/6.x/extend/avenues.html Use this hook to be notified when the application has fully booted, including all plugins. This replaces the legacy EVENT_AFTER_LOAD_PLUGINS event. ```php app()->booted($callback); ``` -------------------------------- ### Import Plaintext Postgres Backup to Craft Cloud Source: https://craftcms.com/docs/cloud/databases Import a plaintext format Postgres backup using the psql command. The --password flag prompts for the password. ```bash psql \ --host="{hostname}" \ --username="{username}" \ --dbname "{database}" \ --password \ < path/to/backup.sql ``` -------------------------------- ### Retrieve GET/POST Data in Twig Source: https://craftcms.com/docs/6.x/upgrade.html Use `app('request').get('paramName')` to retrieve data from a GET query string or POST body, replacing `craft.app.request.getQueryParam()`. ```twig app('request').get('paramName') ``` -------------------------------- ### Accessing Services in Craft 2.x and 3.x-5.x Source: https://craftcms.com/docs/6.x/extend/services.html Illustrates the historical methods for accessing services in older versions of Craft CMS. Craft 2.x used a global access method, while Craft 3.x-5.x utilized plugin instances. ```php # Craft 2.x — instances available via Craft: craft()->activityPlugin_reportsService->generate(...); # Craft 3.x–5.x — singletons available via plugin: Activity::getInstance()->getReports()->generate(...); ``` -------------------------------- ### Displaying Old Input Values in Twig Source: https://craftcms.com/docs/6.x/extend/session This example shows how to repopulate form fields with previously submitted values using the `old()` function, which is useful after validation errors. ```twig {{ input('text', 'name', old('title')) }} ``` -------------------------------- ### Create Project Directory Source: https://craftcms.com/docs/6.x/install.html Use this command to create a new directory for your Craft CMS project and navigate into it. ```bash mkdir my-craft-project cd my-craft-project/ ``` -------------------------------- ### Accessing Artifact Base URL in Node.js Source: https://craftcms.com/docs/cloud/builds Access the `CRAFT_CLOUD_ARTIFACT_BASE_URL` environment variable in Node.js to get the base URL for build artifacts. This variable is not available in browser environments. ```javascript process.env.CRAFT_CLOUD_ARTIFACT_BASE_URL ``` -------------------------------- ### Accessing Project Config via Application Container Source: https://craftcms.com/docs/6.x/extend/services.html When not in a DI-capable context, services like `ProjectConfig` can be accessed using the `app()` helper function, which retrieves the service from the application container. ```php app(\CraftCms\Cms\ProjectConfig\ProjectConfig)->get('graphql.enabled'); ``` -------------------------------- ### List Routes with Middleware Details Source: https://craftcms.com/docs/6.x/extend/http.html Use the `route:list` Artisan command to view route configurations, including middleware groups. The `-v` flag shows middleware groups, and `-vv` lists all applied middleware classes. ```bash # -v displays middleware groups for each route. # -vv lists *every middleware class* that will apply to each route. ddev artisan route:list --path=some-path-segment -v ``` -------------------------------- ### Registering a Dashboard Widget using HasWidgets Trait Source: https://craftcms.com/docs/6.x/extend/avenues.html Example of how to add a custom dashboard widget by defining the $widgets property in your main plugin class, utilizing the HasWidgets trait. ```php use CraftCms\Cms\Plugin\Plugin; use MyOrg\MyPlugin\Widgets\RandomQuoteWidget; class InspirationPlugin extends Plugin { // ... protected array $widgets = [ RandomQuoteWidget::class, ]; } ``` -------------------------------- ### Registering Recurring Tasks with the Scheduler Source: https://craftcms.com/docs/6.x/extend/scheduling.html Implement the `schedule()` method in your plugin's base class to define recurring tasks. This example shows how to schedule a command, a job, and a closure. ```php protected function schedule(Schedule $schedule): void { // Send a test email every day: $schedule->command('craft:mailer:test') ->daily(); // Queue a report at the top of every hour: $schedule->job(new GenerateReport(['templateId' => 1234])) ->hourly(); // Report $schedule->call(function () { Log::info('Scheduler healthy!'); }) ->everyTenMinutes() ->thenPing(env('UPTIME_KUMA_SERVICE_URL')); } ``` -------------------------------- ### Import MySQL Backup to Craft Cloud Source: https://craftcms.com/docs/cloud/databases Use this command to import MySQL backups from Craft or Cloud. Ensure your mysql client version matches the dump's creation version. ```bash mysql \ --host={cloud-db-hostname} \ --port=3306 \ --user={username} \ --password={password} \ --database={database} \ < path/to/backup.sql ``` -------------------------------- ### Query Blog Entry and Site Info with GraphQL Source: https://craftcms.com/docs/getting-started-tutorial/more/graphql.html Use this GraphQL query to fetch a specific blog entry by its slug and retrieve site information. It demonstrates date formatting, accessing nested assets and content blocks, and applying Markdown to rich text fields. ```graphql { entry(slug: "my-trip-to-bend") { title postDate @formatDateTime(format: "d M Y") postDateAlt: postDate @formatDateTime(format: "Y-m-d") url ... on post_Entry { featureImage { title url sized: url @transform(width: 900, height: 600, quality: 90) } postContent { ... on text_Entry { typeHandle text } ... on image_Entry { typeHandle image { title url } } } postCategories { title url } } } globalSet(handle: ["siteInfo"]) { ... on siteInfo_GlobalSet { description @markdown } } } ``` -------------------------------- ### Validation with Middleware Source: https://craftcms.com/docs/6.x/extend/validation.html Implement validation logic within middleware to protect routes or process incoming data before it reaches the controller. This example uses a custom `SharingTokenRule` to validate a 'share' token. ```php use Illuminate\Http\Request; use Illuminate\Support\Closure; use Symfony\Component\HttpFoundation\Response; class HandleShareRequest { public function handle(Request $request, Closure $next): Response { $shareToken = $request->string('share'); if (! $shareToken) { return $next($request); } $validator = new SharingTokenRule(minAge: 'P1W'); abort_unless($validator->validate($shareToken), t('The provided sharing token is not valid.', category: 'activity')); return $next($request); } } ```