diff --git a/.devcontainer/cli/Dockerfile b/.devcontainer/cli/Dockerfile
index ab13330..0317097 100644
--- a/.devcontainer/cli/Dockerfile
+++ b/.devcontainer/cli/Dockerfile
@@ -9,8 +9,7 @@ RUN apk add --no-cache composer
# Add Chromium and Image Magick for puppeteer.
RUN apk add --no-cache \
imagemagick-dev \
- chromium \
- libzip-dev
+ chromium
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PUPPETEER_DOCKER=1
@@ -20,7 +19,7 @@ RUN chmod 777 /usr/src/php/ext/imagick
RUN curl -fsSL https://github.com/Imagick/imagick/archive/refs/tags/3.8.0.tar.gz | tar xvz -C "/usr/src/php/ext/imagick" --strip 1
# Install PHP extensions
-RUN docker-php-ext-install imagick zip
+RUN docker-php-ext-install imagick
# Composer uses its php binary, but we want it to use the container's one
RUN rm -f /usr/bin/php84
diff --git a/.devcontainer/fpm/Dockerfile b/.devcontainer/fpm/Dockerfile
index 3e658b6..8c585c8 100644
--- a/.devcontainer/fpm/Dockerfile
+++ b/.devcontainer/fpm/Dockerfile
@@ -14,8 +14,7 @@ RUN apk add --no-cache \
nodejs \
npm \
imagemagick-dev \
- chromium \
- libzip-dev
+ chromium
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PUPPETEER_DOCKER=1
@@ -25,7 +24,7 @@ RUN chmod 777 /usr/src/php/ext/imagick
RUN curl -fsSL https://github.com/Imagick/imagick/archive/refs/tags/3.8.0.tar.gz | tar xvz -C "/usr/src/php/ext/imagick" --strip 1
# Install PHP extensions
-RUN docker-php-ext-install imagick zip
+RUN docker-php-ext-install imagick
RUN rm -f /usr/bin/php84
RUN ln -s /usr/local/bin/php /usr/bin/php84
diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php
index 6f5d88b..9132d6c 100644
--- a/app/Models/Plugin.php
+++ b/app/Models/Plugin.php
@@ -62,11 +62,6 @@ class Plugin extends Model
$model->current_image = null;
}
});
-
- // Sanitize configuration template on save
- static::saving(function ($model): void {
- $model->sanitizeTemplate();
- });
}
public function user()
@@ -74,25 +69,6 @@ class Plugin extends Model
return $this->belongsTo(User::class);
}
- // sanitize configuration template descriptions and help texts (since they allow HTML rendering)
- protected function sanitizeTemplate(): void
- {
- $template = $this->configuration_template;
-
- if (isset($template['custom_fields']) && is_array($template['custom_fields'])) {
- foreach ($template['custom_fields'] as &$field) {
- if (isset($field['description'])) {
- $field['description'] = \Stevebauman\Purify\Facades\Purify::clean($field['description']);
- }
- if (isset($field['help_text'])) {
- $field['help_text'] = \Stevebauman\Purify\Facades\Purify::clean($field['help_text']);
- }
- }
-
- $this->configuration_template = $template;
- }
- }
-
public function hasMissingRequiredConfigurationFields(): bool
{
if (! isset($this->configuration_template['custom_fields']) || empty($this->configuration_template['custom_fields'])) {
diff --git a/composer.json b/composer.json
index 0ced4da..f801679 100644
--- a/composer.json
+++ b/composer.json
@@ -26,7 +26,6 @@
"livewire/volt": "^1.7",
"om/icalparser": "^3.2",
"spatie/browsershot": "^5.0",
- "stevebauman/purify": "^6.3",
"symfony/yaml": "^7.3",
"wnx/sidecar-browsershot": "^2.6"
},
diff --git a/composer.lock b/composer.lock
index 9767a0d..b9e0495 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "25c2a1a4a2f2594adefe25ddb6a072fb",
+ "content-hash": "4d958d48655a5ad9e3de6b4a9fb52b0a",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -814,67 +814,6 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
- {
- "name": "ezyang/htmlpurifier",
- "version": "v4.19.0",
- "source": {
- "type": "git",
- "url": "https://github.com/ezyang/htmlpurifier.git",
- "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
- "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
- "shasum": ""
- },
- "require": {
- "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
- },
- "require-dev": {
- "cerdic/css-tidy": "^1.7 || ^2.0",
- "simpletest/simpletest": "dev-master"
- },
- "suggest": {
- "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
- "ext-bcmath": "Used for unit conversion and imagecrash protection",
- "ext-iconv": "Converts text to and from non-UTF-8 encodings",
- "ext-tidy": "Used for pretty-printing HTML"
- },
- "type": "library",
- "autoload": {
- "files": [
- "library/HTMLPurifier.composer.php"
- ],
- "psr-0": {
- "HTMLPurifier": "library/"
- },
- "exclude-from-classmap": [
- "/library/HTMLPurifier/Language/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-2.1-or-later"
- ],
- "authors": [
- {
- "name": "Edward Z. Yang",
- "email": "admin@htmlpurifier.org",
- "homepage": "http://ezyang.com"
- }
- ],
- "description": "Standards compliant HTML filter written in PHP",
- "homepage": "http://htmlpurifier.org/",
- "keywords": [
- "html"
- ],
- "support": {
- "issues": "https://github.com/ezyang/htmlpurifier/issues",
- "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
- },
- "time": "2025-10-17T16:34:55+00:00"
- },
{
"name": "firebase/php-jwt",
"version": "v6.11.1",
@@ -5008,72 +4947,6 @@
],
"time": "2025-01-13T13:04:43+00:00"
},
- {
- "name": "stevebauman/purify",
- "version": "v6.3.1",
- "source": {
- "type": "git",
- "url": "https://github.com/stevebauman/purify.git",
- "reference": "3acb5e77904f420ce8aad8fa1c7f394e82daa500"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/stevebauman/purify/zipball/3acb5e77904f420ce8aad8fa1c7f394e82daa500",
- "reference": "3acb5e77904f420ce8aad8fa1c7f394e82daa500",
- "shasum": ""
- },
- "require": {
- "ezyang/htmlpurifier": "^4.17",
- "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "php": ">=7.4"
- },
- "require-dev": {
- "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
- "phpunit/phpunit": "^8.0|^9.0|^10.0|^11.5.3"
- },
- "type": "library",
- "extra": {
- "laravel": {
- "aliases": {
- "Purify": "Stevebauman\\Purify\\Facades\\Purify"
- },
- "providers": [
- "Stevebauman\\Purify\\PurifyServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Stevebauman\\Purify\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Steve Bauman",
- "email": "steven_bauman@outlook.com"
- }
- ],
- "description": "An HTML Purifier / Sanitizer for Laravel",
- "keywords": [
- "Purifier",
- "clean",
- "cleaner",
- "html",
- "laravel",
- "purification",
- "purify"
- ],
- "support": {
- "issues": "https://github.com/stevebauman/purify/issues",
- "source": "https://github.com/stevebauman/purify/tree/v6.3.1"
- },
- "time": "2025-05-21T16:53:09+00:00"
- },
{
"name": "symfony/clock",
"version": "v8.0.0",
diff --git a/package-lock.json b/package-lock.json
index e722432..8411d6a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "laravel",
+ "name": "laravel-trmnl-server",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -156,6 +156,7 @@
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
@@ -192,6 +193,7 @@
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
@@ -213,6 +215,7 @@
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
"integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
@@ -715,6 +718,7 @@
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@lezer/common": "^1.3.0"
}
@@ -1610,6 +1614,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -1893,7 +1898,8 @@
"version": "0.0.1521046",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz",
"integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==",
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/dunder-proto": {
"version": "1.0.1",
@@ -2945,6 +2951,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -2971,6 +2978,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -3421,6 +3429,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
"integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
diff --git a/resources/css/app.css b/resources/css/app.css
index 30cb7a1..46b9ca1 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -59,10 +59,6 @@
@apply !mb-0 !leading-tight;
}
-[data-flux-description] a {
- @apply text-accent underline hover:opacity-80;
-}
-
input:focus[data-flux-control],
textarea:focus[data-flux-control],
select:focus[data-flux-control] {
diff --git a/resources/views/livewire/plugins/recipe.blade.php b/resources/views/livewire/plugins/recipe.blade.php
index ec53aae..4be96cc 100644
--- a/resources/views/livewire/plugins/recipe.blade.php
+++ b/resources/views/livewire/plugins/recipe.blade.php
@@ -37,7 +37,6 @@ new class extends Component {
public array $configuration = [];
public array $xhrSelectOptions = [];
public array $searchQueries = [];
- public array $multiValues = [];
public function mount(): void
{
@@ -75,25 +74,6 @@ new class extends Component {
$this->fillformFields();
$this->data_payload_updated_at = $this->plugin->data_payload_updated_at;
-
- foreach ($this->configuration_template['custom_fields'] ?? [] as $field) {
- if (($field['field_type'] ?? null) !== 'multi_string') {
- continue;
- }
-
- $fieldKey = $field['keyname'] ?? $field['key'] ?? $field['name'];
-
- // Get the existing value from the plugin's configuration
- $rawValue = $this->configuration[$fieldKey] ?? ($field['default'] ?? '');
-
- $currentValue = is_array($rawValue) ? '' : (string)$rawValue;
-
- // Split CSV into array for UI boxes
- $this->multiValues[$fieldKey] = $currentValue !== ''
- ? array_values(array_filter(explode(',', $currentValue)))
- : [''];
- }
-
}
public function fillFormFields(): void
@@ -149,19 +129,6 @@ new class extends Component {
$validated = $this->validate();
$validated['data_payload'] = json_decode(Arr::get($validated,'data_payload'), true);
$this->plugin->update($validated);
-
- foreach ($this->configuration_template as $fieldKey => $field) {
- if (($field['field_type'] ?? null) !== 'multi_string') {
- continue;
- }
-
- if (!isset($this->multiValues[$fieldKey])) {
- continue;
- }
-
- $validated[$fieldKey] = implode(',', array_filter(array_map('trim', $this->multiValues[$fieldKey])));
- }
-
}
protected function validatePollingUrl(): void
@@ -291,21 +258,13 @@ new class extends Component {
{
abort_unless(auth()->user()->plugins->contains($this->plugin), 403);
- foreach ($this->configuration_template['custom_fields'] ?? [] as $field) {
- $fieldKey = $field['keyname'] ?? $field['key'] ?? $field['name'];
-
- if ($field['field_type'] === 'multi_string' && isset($this->multiValues[$fieldKey])) {
- // Join the boxes into a CSV string, trimming whitespace and filtering empties
- $this->configuration[$fieldKey] = implode(',', array_filter(array_map('trim', $this->multiValues[$fieldKey])));
- }
- }
$configurationValues = [];
if (isset($this->configuration_template['custom_fields'])) {
foreach ($this->configuration_template['custom_fields'] as $field) {
$fieldKey = $field['keyname'];
if (isset($this->configuration[$fieldKey])) {
$value = $this->configuration[$fieldKey];
-
+
// For code fields, if the value is a JSON string and the original was an array, decode it
if ($field['field_type'] === 'code' && is_string($value)) {
$decoded = json_decode($value, true);
@@ -315,7 +274,7 @@ new class extends Component {
$value = $decoded;
}
}
-
+
$configurationValues[$fieldKey] = $value;
}
}
@@ -474,22 +433,6 @@ HTML;
$this->loadXhrSelectOptions($fieldKey, $endpoint, $query);
}
}
-
- public function addMultiItem(string $fieldKey): void
- {
- $this->multiValues[$fieldKey][] = '';
- }
-
- public function removeMultiItem(string $fieldKey, int $index): void
- {
- unset($this->multiValues[$fieldKey][$index]);
-
- $this->multiValues[$fieldKey] = array_values($this->multiValues[$fieldKey]);
-
- if (empty($this->multiValues[$fieldKey])) {
- $this->multiValues[$fieldKey][] = '';
- }
- }
}
?>
@@ -696,11 +639,7 @@ HTML;
@php
$fieldKey = $field['keyname'] ?? $field['key'] ?? $field['name'];
$rawValue = $configuration[$fieldKey] ?? ($field['default'] ?? '');
-
- # These are sanitized at Model/Plugin level, safe to render HTML
- $safeDescription = $field['description'] ?? '';
- $safeHelp = $field['help_text'] ?? '';
-
+
// For code fields, if the value is an array, JSON encode it
if ($field['field_type'] === 'code' && is_array($rawValue)) {
$currentValue = json_encode($rawValue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
@@ -718,211 +657,176 @@ HTML;
@endif
@if($field['field_type'] === 'string' || $field['field_type'] === 'url')
-