🆙 Add cms i using 🆙

This commit is contained in:
Remco committed 2025-11-25 22:42:56 +01:00
1 parent 94704e0925
commit d44196149e
35591 files changed
+3601123

No files matched your search

@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Validation {
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
class Rule {
public static function turnstile(): Turnstile {}
}
}
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) ryangjchandler <support@ryangjchandler.co.uk>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,63 @@
# Upgrading from 2.x to 3.0
This document contains notable breaking changes and the steps required to successfully upgrade your application from 2.x to 3.0.
## `@turnstileScripts` removed in favor of component
The `@turnstileScripts` directive has been removed from the package and replaced with a Blade component instead.
```diff
- @turnstileScripts
+ <x-turnstile.scripts />
```
This syntax is now inline with the Turnstile component itself and improves the consistency of the package.
## `turnstile` rule removed
The `turnstile` stringified validation rule provided this package has now been removed in favor of the `Turnstile` rule object.
```diff
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
$request->validate([
'cf-turnstile-response' => [
'required',
- 'turnstile',
+ new Turnstile,
]
]);
```
## `Rule::turnstile()` macro removed
The `Rule::turnstile()` macro provided this package has now been removed in favor of the `Turnstile` rule object.
```diff
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
$request->validate([
'cf-turnstile-response' => [
'required',
- Rule::turnstile(),
+ new Turnstile,
]
]);
```
## `TurnstileClient` class renamed
`TurnstileClient` has been renamed to just `Client`.
```diff
- use RyanChandler\LaravelCloudflareTurnstile\TurnstileClient;
+ use RyanChandler\LaravelCloudflareTurnstile\Client;
```
## `Client` is now a scoped singleton
The `Client` was previously registered as a singleton, but to ensure compatibility with Octane and other single-boot lifecycles, the `Client` is now a scoped singleton.
This shouldn't have a huge impact on userland code, but if you're interacting with this manually there could be breaking changes in behaviour.
Be sure to thoroughly test your application code to find breaking changes.
@@ -0,0 +1,73 @@
{
"name": "ryangjchandler/laravel-cloudflare-turnstile",
"description": "A simple package to help integrate Cloudflare Turnstile.",
"keywords": [
"ryangjchandler",
"laravel",
"laravel-cloudflare-turnstile"
],
"homepage": "https://github.com/ryangjchandler/laravel-cloudflare-turnstile",
"license": "MIT",
"authors": [
{
"name": "Ryan Chandler",
"email": "[email protected]",
"role": "Developer"
}
],
"require": {
"php": "^8.2",
"spatie/laravel-package-tools": "^1.13.0",
"illuminate/contracts": "^11.0|^12.0"
},
"require-dev": {
"laravel/pint": "^1.0",
"nunomaduro/collision": "^8.0",
"larastan/larastan": "^3.1",
"orchestra/testbench": "^9.0|^10.0",
"pestphp/pest": "^3.7",
"pestphp/pest-plugin-laravel": "^3.1",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
"phpstan/phpstan-phpunit": "^1.0|^2.0",
"phpunit/phpunit": "^10.0|^11.5.3",
"spatie/laravel-ray": "^1.26",
"spatie/pest-plugin-snapshots": "^2.0"
},
"autoload": {
"psr-4": {
"RyanChandler\\LaravelCloudflareTurnstile\\": "src",
"RyanChandler\\LaravelCloudflareTurnstile\\Database\\Factories\\": "database/factories"
}
},
"autoload-dev": {
"psr-4": {
"RyanChandler\\LaravelCloudflareTurnstile\\Tests\\": "tests"
}
},
"scripts": {
"analyse": "vendor/bin/phpstan analyse --memory-limit=-1",
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage",
"format": "vendor/bin/pint"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"phpstan/extension-installer": true
}
},
"extra": {
"laravel": {
"providers": [
"RyanChandler\\LaravelCloudflareTurnstile\\LaravelCloudflareTurnstileServiceProvider"
],
"aliases": {
"LaravelCloudflareTurnstile": "RyanChandler\\LaravelCloudflareTurnstile\\Facades\\LaravelCloudflareTurnstile"
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
@@ -0,0 +1,12 @@
<?php
return [
'missing-input-secret' => 'لم يتم إرسال البيانات اللازمة للتحقق من Captcha.',
'invalid-input-secret' => 'البيانات اللازمة للتحقق من Captcha غير صحيحة أو غير موجودة.',
'missing-input-response' => 'لم يتم إرسال الرد المطلوب للتحقق من Captcha.',
'invalid-input-response' => 'الرد المطلوب للتحقق من Captcha غير صالح أو انتهت صلاحيته.',
'bad-request' => 'تم رفض الطلب لأنه غير مكتمل أو يحتوي على خطأ.',
'timeout-or-duplicate' => 'تم استخدام رد Captcha هذا مسبقًا.',
'internal-error' => 'حدث خطأ داخلي أثناء التحقق من Captcha.',
'unexpected' => 'حدث خطأ غير متوقع أثناء التحقق من Captcha.',
];
@@ -0,0 +1,12 @@
<?php
return [
'missing-input-secret' => 'The secret parameter was not passed.',
'invalid-input-secret' => 'The secret parameter was invalid or did not exist.',
'missing-input-response' => 'The response parameter was not passed.',
'invalid-input-response' => 'The response parameter is invalid or has expired.',
'bad-request' => 'The request was rejected because it was malformed.',
'timeout-or-duplicate' => 'The response parameter has already been validated before.',
'internal-error' => 'An internal error happened while validating the response.',
'unexpected' => 'An unexpected error occurred.',
];
@@ -0,0 +1 @@
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer {{ $attributes }}></script>
@@ -0,0 +1,45 @@
@props([
'id' => 'captcha',
])
@php
if (! preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $id)) {
throw new InvalidArgumentException("The Turnstile ID [{$id}] must start start with a letter or underscore, and can only contain alphanumeric or underscore characters.");
}
$model = $attributes->has('wire:model') ? $attributes->get('wire:model') : null;
@endphp
<div
data-sitekey="{{ config('services.turnstile.key') }}"
@if ($model)
wire:ignore
data-callback="{{ $id }}Callback"
data-expired-callback="{{ $id }}ExpiredCallback"
data-timeout-callback="{{ $id }}ExpiredCallback"
{{ $attributes->filter(fn($value, $key) => ! in_array($key, ['data-callback', 'data-expired-callback', 'data-timeout-callback', 'wire:model', 'id']))->class(['cf-turnstile']) }}
@else
{{ $attributes->class(['cf-turnstile']) }}
@endif
></div>
@if ($model)
<script>
document.addEventListener('livewire:initialized', () => {
window.{{ $id }}Callback = function (token) {
@this.set("{{ $model }}", token);
}
window.{{ $id }}ExpiredCallback = function () {
window.turnstile.reset();
}
@this.watch("{{ $model }}", (value, old) => {
// If there was a value, and now there isn't, reset the Turnstile.
if (!!old && !value) {
window.turnstile.reset();
}
})
});
</script>
@endif
@@ -0,0 +1,36 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile;
use Illuminate\Support\Facades\Http;
use RyanChandler\LaravelCloudflareTurnstile\Contracts\ClientInterface;
use RyanChandler\LaravelCloudflareTurnstile\Responses\SiteverifyResponse;
class Client implements ClientInterface
{
public function __construct(
protected string $secret,
) {}
public function siteverify(string $response): SiteverifyResponse
{
$response = Http::retry(3, 100)
->asForm()
->acceptJson()
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'secret' => config('services.turnstile.secret'),
'response' => $response,
]);
if (! $response->ok()) {
return SiteverifyResponse::success();
}
return SiteverifyResponse::failure($response->json('error-codes'));
}
public function dummy(): string
{
return self::RESPONSE_DUMMY_TOKEN;
}
}
@@ -0,0 +1,12 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\Contracts;
use RyanChandler\LaravelCloudflareTurnstile\Responses\SiteverifyResponse;
interface ClientInterface
{
const RESPONSE_DUMMY_TOKEN = 'XXXX.DUMMY.TOKEN.XXXX';
public function siteverify(string $response): SiteverifyResponse;
}
@@ -0,0 +1,26 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\Facades;
use Illuminate\Support\Facades\Facade;
use RyanChandler\LaravelCloudflareTurnstile\Contracts\ClientInterface;
use RyanChandler\LaravelCloudflareTurnstile\Testing\FakeClient;
/**
* @method static string dummy()
* @method static \RyanChandler\LaravelCloudflareTurnstile\Responses\SiteverifyResponse siteverify(string $response)
*/
class Turnstile extends Facade
{
protected static function getFacadeAccessor(): string
{
return ClientInterface::class;
}
public static function fake(): FakeClient
{
static::swap($fake = new FakeClient);
return $fake;
}
}
@@ -0,0 +1,40 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use RyanChandler\LaravelCloudflareTurnstile\Contracts\ClientInterface;
use RyanChandler\LaravelCloudflareTurnstile\View\Components\Scripts;
use RyanChandler\LaravelCloudflareTurnstile\View\Components\Turnstile as TurnstileComponent;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
class LaravelCloudflareTurnstileServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('laravel-cloudflare-turnstile')
->hasTranslations()
->hasViews();
}
public function packageRegistered(): void
{
$this->app->scoped(ClientInterface::class, static function (Application $app): Client {
return new Client($app['config']->get('services.turnstile.secret'));
});
}
public function packageBooted(): void
{
$this->bootBlade();
}
private function bootBlade(): void
{
Blade::component('turnstile.scripts', Scripts::class);
Blade::component('turnstile', TurnstileComponent::class);
}
}
@@ -0,0 +1,31 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\Responses;
use Illuminate\Contracts\Support\Arrayable;
class SiteverifyResponse implements Arrayable
{
public function __construct(
public readonly bool $success,
public readonly array $errorCodes,
) {}
public static function success(): self
{
return new self(true, []);
}
public static function failure(array $errorCodes = []): self
{
return new self(false, $errorCodes);
}
public function toArray(): array
{
return [
'success' => $this->success,
'error-codes' => $this->errorCodes,
];
}
}
@@ -0,0 +1,34 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use RyanChandler\LaravelCloudflareTurnstile\Facades\Turnstile as Facade;
class Turnstile implements ValidationRule
{
protected array $messages = [];
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$response = Facade::siteverify($value);
if ($response->success) {
return;
}
foreach ($response->errorCodes as $errorCode) {
$fail(match ($errorCode) {
'missing-input-secret' => __('cloudflare-turnstile::errors.missing-input-secret'),
'invalid-input-secret' => __('cloudflare-turnstile::errors.invalid-input-secret'),
'missing-input-response' => __('cloudflare-turnstile::errors.missing-input-response'),
'invalid-input-response' => __('cloudflare-turnstile::errors.invalid-input-response'),
'bad-request' => __('cloudflare-turnstile::errors.bad-request'),
'timeout-or-duplicate' => __('cloudflare-turnstile::errors.timeout-or-duplicate'),
'internal-error' => __('cloudflare-turnstile::errors.internal-error'),
default => __('cloudflare-turnstile::errors.unexpected'),
});
}
}
}
@@ -0,0 +1,53 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\Testing;
use Illuminate\Support\Testing\Fakes\Fake;
use RyanChandler\LaravelCloudflareTurnstile\Contracts\ClientInterface;
use RyanChandler\LaravelCloudflareTurnstile\Responses\SiteverifyResponse;
class FakeClient implements ClientInterface, Fake
{
protected bool $shouldPass = true;
protected bool $isExpired = false;
public function fail(): self
{
$this->shouldPass = false;
return $this;
}
public function expired(): self
{
$this->isExpired = true;
return $this;
}
public function pass(): self
{
$this->shouldPass = true;
return $this;
}
public function siteverify(string $response): SiteverifyResponse
{
if ($this->isExpired) {
return SiteverifyResponse::failure(['timeout-or-duplicate']);
}
if (! $this->shouldPass) {
return SiteverifyResponse::failure(['invalid-input-response']);
}
return SiteverifyResponse::success();
}
public function dummy(): string
{
return self::RESPONSE_DUMMY_TOKEN;
}
}
@@ -0,0 +1,14 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\View\Components;
use Illuminate\View\Component;
class Scripts extends Component
{
public function render()
{
// @phpstan-ignore-next-line argument.type
return view('cloudflare-turnstile::components.scripts');
}
}
@@ -0,0 +1,14 @@
<?php
namespace RyanChandler\LaravelCloudflareTurnstile\View\Components;
use Illuminate\View\Component;
class Turnstile extends Component
{
public function render()
{
// @phpstan-ignore argument.type
return view('cloudflare-turnstile::components.turnstile');
}
}