8 Commits
Author SHA1 Message Date
mathiaspalmqvist 6f0c4ad4a8 Merge pull request 'Drop Laravel 11, add PHP 8.5 and merge CI workflows' (#22) from laravel-12-13-ci into main
CI / PHPStan (push) Successful in 49s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-lowest) (push) Successful in 42s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-stable) (push) Successful in 37s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-lowest) (push) Successful in 41s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-stable) (push) Successful in 46s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-lowest) (push) Successful in 48s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-stable) (push) Successful in 43s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-lowest) (push) Successful in 39s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-stable) (push) Successful in 42s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-lowest) (push) Successful in 39s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-stable) (push) Successful in 45s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-lowest) (push) Successful in 47s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-stable) (push) Successful in 43s
Reviewed-on: #22
2026-11-12 10:07:41 +00:00
mathiaspalmqvist d42263023f Move workflows to .gitea/workflows
dependabot-auto-merge / dependabot (pull_request_target) Skipped
CI / PHPStan (push) Successful in 45s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-stable) (push) Successful in 38s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-lowest) (push) Successful in 41s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-lowest) (push) Successful in 41s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-lowest) (push) Successful in 45s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-lowest) (push) Successful in 44s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-stable) (push) Successful in 42s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-stable) (push) Successful in 41s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-lowest) (push) Successful in 46s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-stable) (push) Successful in 45s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-stable) (push) Successful in 43s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-lowest) (push) Successful in 43s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-stable) (push) Successful in 44s
2026-11-12 10:51:27 +01:00
mathiaspalmqvist 7af348cbb4 Name the test matrix jobs Pest
dependabot-auto-merge / dependabot (pull_request_target) Skipped
CI / PHPStan (push) Successful in 47s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.3, Laravel 12.*, prefer-stable) (push) Canceled after 0s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.4, Laravel 12.*, prefer-stable) (push) Canceled after 0s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.5, Laravel 12.*, prefer-stable) (push) Canceled after 0s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.3, Laravel 13.*, prefer-stable) (push) Canceled after 0s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.4, Laravel 13.*, prefer-stable) (push) Canceled after 0s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-lowest) (push) Canceled after 0s
CI / Pest (PHP 8.5, Laravel 13.*, prefer-stable) (push) Canceled after 0s
2026-11-12 10:39:56 +01:00
mathiaspalmqvist 6d9206df80 Install PHPStan dependencies without the Composer cache action
dependabot-auto-merge / dependabot (pull_request_target) Skipped
CI / PHPStan (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-stable (push) Canceled after 0s
The runner cannot reach the Actions cache service, so every restore and
save timed out without ever producing a cache hit.
2026-11-12 10:38:14 +01:00
mathiaspalmqvist ded8967551 Point repository links at the new 559342-9607 owner
dependabot-auto-merge / dependabot (pull_request_target) Skipped
CI / PHPStan (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-stable (push) Canceled after 0s
2026-11-12 10:27:28 +01:00
mathiaspalmqvist e3a0aa54e9 Drop Laravel 11, add PHP 8.5 and merge CI workflows
dependabot-auto-merge / dependabot (pull_request_target) Skipped
CI / PHPStan (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L12.* - prefer-stable (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.3 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.4 - L13.* - prefer-stable (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-lowest (push) Canceled after 0s
CI / P8.5 - L13.* - prefer-stable (push) Canceled after 0s
- Require Laravel 12 or 13 and drop orchestra/testbench 9
- Add PHP 8.5 to the test matrix
- Merge the test and PHPStan workflows into a single CI workflow,
  running PHPStan before the test matrix
- Add manual dispatch and cancel superseded runs
- Update requirements and badge in README
2026-11-12 10:23:16 +01:00
mathiaspalmqvist 16257e3383 Merge pull request 'Accept text/plain events from the Plausible tracker' (#21) from fix-text-plain-events into main
run-tests / P8.3 - L11.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
Fix PHP code style issues / php-code-styling (push) Canceled after 0s
PHPStan / phpstan (push) Canceled after 2s
run-tests / P8.3 - L11.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L12.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L12.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L13.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L13.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L11.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L11.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L12.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L12.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L13.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L13.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
Reviewed-on: #21
2026-11-12 08:21:32 +00:00
mathiaspalmqvist a97baf5921 Accept text/plain events from the Plausible tracker
dependabot-auto-merge / dependabot (pull_request_target) Skipped
Fix PHP code style issues / php-code-styling (push) Successful in 6s
PHPStan / phpstan (push) Successful in 2m38s
run-tests / P8.3 - L11.* - prefer-lowest - ubuntu-latest (push) Failing after 33s
run-tests / P8.4 - L11.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L11.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L12.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L12.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L12.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L12.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L13.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L13.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L13.* - prefer-lowest - ubuntu-latest (push) Canceled after 0s
run-tests / P8.4 - L13.* - prefer-stable - ubuntu-latest (push) Canceled after 0s
run-tests / P8.3 - L11.* - prefer-stable - ubuntu-latest (push) Canceled after 1s
The Plausible tracker posts its JSON payload with Content-Type text/plain,
  which Laravel does not parse, so every real event failed validation and
  was redirected instead of being forwarded.

  - Decode the raw body when the request is not JSON
  - Respond with 400 instead of a redirect on invalid non-JSON requests
  - Trim trailing slash from the configured domain when forwarding events
  - Add a 5s timeout and respond with 502 when Plausible is unreachable
2026-11-12 09:18:50 +01:00
11 changed files with 147 additions and 74 deletions

No files matched your search

@@ -1,52 +1,59 @@
name: run-tests
name: CI
on:
push:
paths:
- '**.php'
- '.github/workflows/run-tests.yml'
- '.gitea/workflows/ci.yml'
- 'phpunit.xml.dist'
- 'phpstan.neon.dist'
- 'phpstan-baseline.neon'
- 'composer.json'
- 'composer.lock'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ${{ matrix.os }}
phpstan:
name: PHPStan
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
coverage: none
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Run PHPStan
run: vendor/bin/phpstan --error-format=github
pest:
name: Pest (PHP ${{ matrix.php }}, Laravel ${{ matrix.laravel }}, ${{ matrix.stability }})
needs: phpstan
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
php: [8.3, 8.4]
laravel: [11.*, 12.*, 13.*]
php: ['8.3', '8.4', '8.5']
laravel: ['12.*', '13.*']
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 11.*
php: 8.3
testbench: 9.*
carbon: ^2.63
- laravel: 11.*
php: 8.4
testbench: 9.*
carbon: ^2.63
- laravel: 12.*
php: 8.3
testbench: 10.*
carbon: ^3.8.4
- laravel: 12.*
php: 8.4
testbench: 10.*
carbon: ^3.8.4
- laravel: 13.*
php: 8.3
testbench: 11.*
carbon: ^3.8.4
- laravel: 13.*
php: 8.4
testbench: 11.*
carbon: ^3.8.4
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
- laravel: '12.*'
testbench: '10.*'
- laravel: '13.*'
testbench: '11.*'
steps:
- name: Checkout code
@@ -69,12 +76,12 @@ jobs:
composer require \
"laravel/framework:${{ matrix.laravel }}" \
"orchestra/testbench:${{ matrix.testbench }}" \
"nesbot/carbon:${{ matrix.carbon }}" \
--no-interaction --no-update
"nesbot/carbon:^3.8.4" \
--dev --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: List Installed Dependencies
- name: List installed dependencies
run: composer show -D
- name: Execute tests
File renamed without changes.
+1 -1
View File
@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question or request a feature
url: https://git.mattitja.se/Github/laravel-plausible-proxy/issues
url: https://git.mattitja.se/559342-9607/laravel-plausible-proxy/issues
about: Open an issue in the repository
- name: Report a security issue
url: mailto:hej@mattitja.se
-28
View File
@@ -1,28 +0,0 @@
name: PHPStan
on:
push:
paths:
- '**.php'
- 'phpstan.neon.dist'
- '.github/workflows/phpstan.yml'
jobs:
phpstan:
name: phpstan
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
- name: Install composer dependencies
uses: ramsey/composer-install@v3
- name: Run PHPStan
run: ./vendor/bin/phpstan --error-format=github
+4 -4
View File
@@ -1,16 +1,16 @@
# laravel-plausible-proxy
[![Latest Version on Packagist](https://img.shields.io/packagist/v/mattitja/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
[![Tests](https://git.mattitja.se/Github/laravel-plausible-proxy/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=run-tests.yml)
[![Code Style](https://git.mattitja.se/Github/laravel-plausible-proxy/actions/workflows/fix-php-code-style-issues.yml/badge.svg?branch=main)](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=fix-php-code-style-issues.yml)
[![Tests](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions/workflows/ci.yml/badge.svg?branch=main)](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions?workflow=ci.yml)
[![Code Style](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions/workflows/fix-php-code-style-issues.yml/badge.svg?branch=main)](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions?workflow=fix-php-code-style-issues.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/mattitja/laravel-plausible-proxy.svg?style=flat-square)](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
A Laravel package for proxying Plausible Analytics, improving performance and privacy.
## Requirements
- Laravel 11, 12 or 13
- PHP 8.3 or 8.4
- Laravel 12 or 13
- PHP 8.3, 8.4 or 8.5
## Installation
+3 -3
View File
@@ -7,7 +7,7 @@
"laravel-plausible-proxy",
"plausible"
],
"homepage": "https://git.mattitja.se/Github/laravel-plausible-proxy",
"homepage": "https://git.mattitja.se/559342-9607/laravel-plausible-proxy",
"license": "MIT",
"authors": [
{
@@ -19,13 +19,13 @@
"require": {
"php": "^8.3",
"spatie/laravel-package-tools": "^1.16",
"illuminate/contracts": "^11.0||^12.0||^13.0"
"illuminate/contracts": "^12.0||^13.0"
},
"require-dev": {
"laravel/pint": "^1.14",
"nunomaduro/collision": "^8.1",
"larastan/larastan": "^3.0",
"orchestra/testbench": "^9.0||^10.0||^11.0",
"orchestra/testbench": "^10.0||^11.0",
"pestphp/pest": "^3.0||^4.0",
"pestphp/pest-plugin-arch": "^3.0||^4.0",
"pestphp/pest-plugin-laravel": "^3.0||^4.0",
@@ -2,18 +2,30 @@
namespace Mattitja\LaravelPlausibleProxy\Http\Controllers;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mattitja\LaravelPlausibleProxy\Http\Requests\PlausibleEventRequest;
class PlausibleEventApiController
{
public function __invoke(PlausibleEventRequest $request): Response
{
$upstream = Http::withHeaders([
$url = rtrim((string) config('plausible-proxy.domain'), '/').'/api/event';
try {
$upstream = Http::timeout(5)
->withHeaders([
'User-Agent' => (string) $request->userAgent(),
'X-Forwarded-For' => (string) $request->ip(),
])->post(config('plausible-proxy.domain').'/api/event', $request->all());
])
->post($url, $request->all());
} catch (ConnectionException $e) {
Log::error('Failed to forward Plausible event: '.$e->getMessage());
return response('Bad Gateway', 502);
}
return response($upstream->body(), $upstream->status())
->header('Content-Type', $upstream->header('Content-Type') ?: 'text/plain');
@@ -2,7 +2,9 @@
namespace Mattitja\LaravelPlausibleProxy\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class PlausibleEventRequest extends FormRequest
{
@@ -11,6 +13,32 @@ class PlausibleEventRequest extends FormRequest
return true;
}
/**
* The Plausible tracker sends its JSON payload with a text/plain content
* type, which Laravel does not parse automatically.
*/
protected function prepareForValidation(): void
{
if ($this->isJson()) {
return;
}
$payload = json_decode($this->getContent(), true);
if (is_array($payload)) {
$this->merge($payload);
}
}
protected function failedValidation(Validator $validator): void
{
if (! $this->expectsJson()) {
throw new HttpResponseException(response('Bad Request', 400));
}
parent::failedValidation($validator);
}
public function rules(): array
{
return [
+54
View File
@@ -1,5 +1,6 @@
<?php
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
it('fetches plausible script and caches it', function () {
@@ -145,3 +146,56 @@ it('rejects invalid props', function () {
'p' => 'not-json',
])->assertStatus(422)->assertJsonValidationErrors(['p']);
});
it('accepts events sent as text/plain by the plausible tracker', function () {
Http::fake([
'https://plausible.io/api/event' => Http::response('ok', 202),
]);
$this->call('POST', '/api/event', [], [], [], ['CONTENT_TYPE' => 'text/plain'], json_encode([
'n' => 'pageview',
'u' => 'https://example.com/page',
'd' => 'example.com',
'r' => null,
]))->assertStatus(202);
Http::assertSent(fn ($request) => $request->url() === 'https://plausible.io/api/event'
&& $request['n'] === 'pageview'
&& $request['u'] === 'https://example.com/page'
&& $request['d'] === 'example.com');
});
it('responds with 400 for invalid text/plain events', function () {
Http::fake();
$this->call('POST', '/api/event', [], [], [], ['CONTENT_TYPE' => 'text/plain'], 'not-json')
->assertStatus(400);
Http::assertNothingSent();
});
it('handles a trailing slash in the domain when forwarding events', function () {
config()->set('plausible-proxy.domain', 'https://plausible.example.com/');
Http::fake([
'https://plausible.example.com/api/event' => Http::response('ok', 202),
]);
$this->postJson('/api/event', [
'n' => 'pageview',
'u' => 'https://example.com/page',
'd' => 'example.com',
])->assertStatus(202);
Http::assertSent(fn ($request) => $request->url() === 'https://plausible.example.com/api/event');
});
it('responds with 502 when plausible is unreachable', function () {
Http::fake(fn () => throw new ConnectionException('Connection refused'));
$this->postJson('/api/event', [
'n' => 'pageview',
'u' => 'https://example.com/page',
'd' => 'example.com',
])->assertStatus(502);
});