Skip to content

Commit 5a27f47

Browse files
[2.x] Adds Livewire and Filament PHP helpers.
1 parent 2be0e4c commit 5a27f47

12 files changed

Lines changed: 1883 additions & 155 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
/phpunit.xml export-ignore
1111
/tests export-ignore
1212
/.editorconfig export-ignore
13+
/phpstan.neon export-ignore

.github/workflows/php.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ jobs:
6262
- 13.*
6363
dependencies: [ lowest, highest ]
6464
include-filament: [ true ]
65-
exclude:
66-
- laravel-constraint: "13.*"
67-
include-filament: true
6865

6966
steps:
7067
- name: Set up PHP

README.md

Lines changed: 124 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Finally, you can also set a custom callback name to be executed once the script
107107

108108
#### Site Key on JavaScript frontend
109109

110-
If you put the script on the `<head>` part of your HTML view, you may set the `meta` attribute to render a `<meta>` tag alongside the script. This tag will contain your Turnstile site key so your JavaScript frontend can retrieve use it to render the widget.
110+
If you put the script on the `<head>` part of your HTML view, you may set the `meta` attribute to render a `<meta>` tag alongside the script. This tag will contain your Turnstile site-key so your JavaScript frontend can use it to render the widget.
111111

112112
```blade
113113
<!DOCTYPE html>
@@ -161,11 +161,24 @@ Alternatively, you may set a custom name for the tag by setting a value to the `
161161
<meta name="my-custom-key" content="1x00000000000000000000AA" />
162162
```
163163

164+
#### Preconnect
165+
166+
You can also add a [resource hint to Cloudflare servers](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#2-optional-optimize-performance-with-resource-hints) by setting the `preconnect` attribute in the component.
167+
168+
```blade
169+
<x-turnstile::script preconnect />
170+
```
171+
172+
```html
173+
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer async></script>
174+
<link rel="preconnect" href="https://challenges.cloudflare.com">
175+
```
176+
164177
### Widget
165178

166179
> [!IMPORTANT]
167180
>
168-
> Remember that the Widget Mode is controlled via your [Cloudflare Dashboard](https://dash.cloudflare.com), not here. On development, this is controlled with [testing keys](#testing-keys).
181+
> Remember that the Widget Mode is controlled via your [Cloudflare Dashboard](https://dash.cloudflare.com), not here. In development, this is controlled with [testing keys](#testing-keys).
169182
170183
You can use the `<x-turnstile::widget />` Blade Component to add the Turnstile Widget in your forms. Depending on the Widget Mode, the Widget may render as usual or be invisible at Turnstile discretion.
171184

@@ -840,56 +853,91 @@ return Application::configure(basePath: dirname(__DIR__))
840853
->create();
841854
```
842855
843-
## Livewire & Filament trait
856+
## Livewire Trait
844857

845-
If you're using Liveware or Filament PHP, you may use the `Laragear\Turnstile\Livewire\InteractsWithTurnstile` trait in your Filament Pages or components.
858+
If you're using Liveware, you may use the `Laragear\Turnstile\Livewire\InteractsWithTurnstile` trait in your Livewire pages or components, alongside the [widget](#widget) in your frontend.
846859

847-
The trait will register a hook _after validation_, that only runs on non-Precognitive requests. This way, the Turnstile Challenge is consumed only when the form is completely validated. It also avoids using an idempotency key based on the request fingerprint, saving you a request to Cloudflare Turnstile servers.
860+
The trait overrides the `validate()` method the Component class, and validates the Turnstile Challenge only when all the validation rules pass.
848861

849862
```php
850-
use Filament\Forms\Concerns\InteractsWithForms;
851-
use Filament\Forms\Contracts\HasForms;
852-
use Filament\Pages\Page;
853-
use Filament\Schemas\Schema;
863+
use App\Models\Ball;
854864
use Laragear\Turnstile\Livewire\InteractsWithTurnstile;
865+
use Livewire\Attributes\Rule;
866+
use Livewire\Component;
855867

856-
class MyCustomPage extends Page implements HasForms
868+
class MyCustomPage extends Component
857869
{
858-
use InteractsWithForms;
859870
use InteractsWithTurnstile;
871+
872+
#[Rule('required')]
873+
public $name;
874+
875+
#[Rule('required')]
876+
public $color;
860877

861-
public function form(Schema $schema) : Schema
878+
public function save() : Schema
862879
{
863-
return $schema->components([
864-
// ...
865-
]);
880+
$data = $this->validate();
881+
882+
Ball::create($data);
866883
}
867884
}
868885
```
869886

870-
If you're using a custom challenge key in your form, you may override the `turnstileToken()` method to retrieve the value of the token.
887+
If you're using a custom challenge key in your form, you may override the `turnstileToken()` method to retrieve the value of the token form elsewhere.
871888

872889
```php
890+
public array $data = [];
891+
873892
/**
874893
* Return token for the Turnstile Challenge present in the form.
875894
*
876895
* @return string|null|void
877896
*/
878-
protected function turnstileToken(string $key)
897+
protected function turnstileToken()
879898
{
880899
return $this->data['cloudflare-turnstile-token'];
881900
}
882901
```
883902

884903
> [!IMPORTANT]
885904
>
886-
> The trait injects the Turnstile challenge validation on all forms. If you need more customization, use the [`afterValidate()` hook](https://filamentphp.com/docs/5.x/resources/creating-records#lifecycle-hooks).
905+
> The Challenge validation does not run when calling `validateOnly()`. In that case, you should [validate manually](#livewire-manual-validation)
906+
907+
### Livewire manual validation
908+
909+
To detach the automatic validation of the Challenge, you can use the `validatesTurnstileAutomatically()` method and return `false`. This way, calling `validate()` in your component won't _consume_ the challenge token.
910+
911+
```php
912+
/**
913+
* If the validation should be run automatically after validation.
914+
*/
915+
protected function validatesTurnstileAutomatically(): bool
916+
{
917+
return false;
918+
}
919+
```
920+
921+
After that, you may call `validateTurnstile()` manually in your component.
922+
923+
```php
924+
public function create()
925+
{
926+
$validated = $this->validate();
927+
928+
// ...
929+
930+
$this->validateTurnstile();
931+
932+
Ball::create($validated);
933+
}
934+
```
887935

888936
### Handling the Turnstile Challenge
889937

890938
You have access to some useful methods to handle if the challenge should be deemed successful or not, and how to handle successes and failures:
891939

892-
* `skipTurnstileValidation()`: Returns if the challenge verification should run when authenticated.
940+
* `skipTurnstileValidation()`: Returns if the challenge verification should run or be skipped.
893941
* `handleTurnstileChallengeStatus()`: Returns if the challenge is successful or not.
894942
* `handleSuccessfulTurnstileChallenge()`: Handle a successful Turnstile challenge.
895943
* `handleFailedTurnstileChallenge()`: Handle a failed Turnstile challenge.
@@ -905,6 +953,7 @@ use Laragear\Turnstile\Challenge;
905953
*/
906954
protected function skipTurnstileValidation(): bool
907955
{
956+
// Do not run if the user is an admin.
908957
return auth('admins')->check();
909958
}
910959

@@ -917,6 +966,62 @@ protected function handleTurnstileChallengeStatus(Challenge $challenge): bool
917966
}
918967
```
919968

969+
## Filament Widget Field
970+
971+
If you're using Filament, you may use the `Laragear\Turnstile\Filament\Forms\TurnstileWidget` field in your forms. It will automatically inject the Turnstile Script in the page and render the Turnstile Widget. The Turnstile Challenge will be validated only on complete form submission.
972+
973+
```php
974+
use Filament\Forms\Components\Textarea;
975+
use Filament\Forms\Components\TextInput;
976+
use Filament\Forms\Concerns\InteractsWithForms;
977+
use Filament\Forms\Contracts\HasForms;
978+
use Filament\Pages\Page;
979+
use Filament\Schemas\Schema;
980+
use Laragear\Turnstile\Filament\Forms\TurnstileWidget;
981+
982+
class ContactPage extends Page implements HasForms
983+
{
984+
use InteractsWithForms;
985+
986+
public function form(Schema $schema) : Schema
987+
{
988+
return $schema->components([
989+
TextInput::make('subject')->required(),
990+
Textarea::make('message')->required(),
991+
// Add the Turnstile Widget
992+
TurnstileWidget::make(),
993+
]);
994+
}
995+
}
996+
```
997+
998+
The Widget includes some convenient methods based on the [Cloudflare Turnstile Widget configuration](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/widget-configurations/):
999+
1000+
| Method | Description |
1001+
|------------------------------------|------------------------------------------------------------------------|
1002+
| `normal()` | Sets the size of the widget to normal. |
1003+
| `flexible()` | Sets the size of the widget to flexible. |
1004+
| `compact()` | Sets the size of the widget to compact. |
1005+
| `system()` | Sets the theme of the widget to system/browser default. |
1006+
| `light()` | Sets the theme of the widget to light. |
1007+
| `dark()` | Sets the theme of the widget to dark. |
1008+
| `appearanceAlways()` | Sets the appearance of the widget to always appear. |
1009+
| `appearanceExecute()` | Sets the appearance of the widget to appear on manual execution. |
1010+
| `appearanceInteractionOnly()` | Sets the appearance of the widget to appear only when required. |
1011+
| `executionRender()` | Sets the execution of the widget challenge as it renders. |
1012+
| `executionExecute()` | Sets the execution of the widget challenge on manual execution. |
1013+
| `languageAuto()` | Sets the language of the widget to browser locale. |
1014+
| `language(string $lang)` | Sets the language of the widget to given locale. |
1015+
| `languageApp()` | Sets the language of the widget to application locale. |
1016+
| `tabindex(int $tabindex)` | Sets the tab order of the widget to one set. |
1017+
| `callback(string $functionName)` | Adds an additional function names to execute on successful challenges. |
1018+
| `actionName(string $functionName)` | Sets the action name for the challenge. |
1019+
| `implicit(bool $condition = true)` | Makes the widget be rendered automatically by the script. |
1020+
1021+
> [!IMPORTANT]
1022+
>
1023+
> The Filament Turnstile Widget is rendered **implicitly** by default, meaning its rendering is handled internally by AlpineJS. This makes all the options to be passed as a JavaScript object. You can use **explicit** rendering, but you may have problems if Livewire or Filament are configured to run in [SPA mode](https://filamentphp.com/docs/5.x/panel-configuration#spa-mode) or [use islands](https://livewire.laravel.com/docs/4.x/islands).
1024+
9201025
## Advanced configuration
9211026

9221027
Laragear Turnstile is intended to work out-of-the-box, but you can publish the configuration file for fine-tuning the Challenge verification.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"illuminate/session": "12.*|13.*"
3636
},
3737
"require-dev": {
38-
"filament/filament": "4.*|5.*",
38+
"filament/filament": "5.*",
3939
"orchestra/testbench": "10.*|11.*"
4040
},
4141
"autoload": {

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
ignoreErrors:
3+
- '#Trait Laragear\\Turnstile\\Livewire\\InteractsWithTurnstile is used zero times and is not analysed\.#'

0 commit comments

Comments
 (0)