Compare commits

...

6 commits

Author SHA1 Message Date
Benjamin Nussbaum
770b511290 feat: check recipe compatibility and min_version
Some checks failed
tests / ci (push) Has been cancelled
2025-09-02 17:08:26 +02:00
Benjamin Nussbaum
4bb5723767 fix: normalize key for multiple selects 2025-09-02 15:15:28 +02:00
Benjamin Nussbaum
40ceba267a feat: allow to url_encode array in polling url 2025-09-02 15:08:37 +02:00
Benjamin Nussbaum
aa8d3d1428 fix: show more detailed Liquid exceptions 2025-09-02 12:54:10 +02:00
Benjamin Nussbaum
d999b5157f fix: include Laravel liquid filters (like dd) 2025-09-02 12:53:49 +02:00
Benjamin Nussbaum
d3690c9e10 fix: speedup plugin overview page 2025-09-02 12:23:12 +02:00
7 changed files with 101 additions and 23 deletions

View file

@ -0,0 +1,20 @@
<?php
namespace App\Liquid\Filters;
class StandardFilters extends \Keepsuit\Liquid\Filters\StandardFilters
{
/**
* Converts any URL-unsafe characters in a string to the
* [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
*/
public function urlEncode(string|int|float|array|null $input): string
{
if (is_array($input)) {
$input = json_encode($input);
}
return parent::urlEncode($input);
}
}

View file

@ -7,6 +7,7 @@ use App\Liquid\Filters\Data;
use App\Liquid\Filters\Date; use App\Liquid\Filters\Date;
use App\Liquid\Filters\Localization; use App\Liquid\Filters\Localization;
use App\Liquid\Filters\Numbers; use App\Liquid\Filters\Numbers;
use App\Liquid\Filters\StandardFilters;
use App\Liquid\Filters\StringMarkup; use App\Liquid\Filters\StringMarkup;
use App\Liquid\Filters\Uniqueness; use App\Liquid\Filters\Uniqueness;
use App\Liquid\Tags\TemplateTag; use App\Liquid\Tags\TemplateTag;
@ -18,6 +19,7 @@ use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Keepsuit\LaravelLiquid\LaravelLiquidExtension;
use Keepsuit\Liquid\Exceptions\LiquidException; use Keepsuit\Liquid\Exceptions\LiquidException;
use Keepsuit\Liquid\Extensions\StandardExtension; use Keepsuit\Liquid\Extensions\StandardExtension;
@ -264,6 +266,7 @@ class Plugin extends Model
// Use the Liquid template engine to resolve variables // Use the Liquid template engine to resolve variables
$environment = App::make('liquid.environment'); $environment = App::make('liquid.environment');
$environment->filterRegistry->register(StandardFilters::class);
$liquidTemplate = $environment->parseString($template); $liquidTemplate = $environment->parseString($template);
$context = $environment->newRenderContext(data: $variables); $context = $environment->newRenderContext(data: $variables);
@ -285,7 +288,7 @@ class Plugin extends Model
$inlineFileSystem = new InlineTemplatesFileSystem(); $inlineFileSystem = new InlineTemplatesFileSystem();
$environment = new \Keepsuit\Liquid\Environment( $environment = new \Keepsuit\Liquid\Environment(
fileSystem: $inlineFileSystem, fileSystem: $inlineFileSystem,
extensions: [new StandardExtension()] extensions: [new StandardExtension(), new LaravelLiquidExtension()]
); );
// Register all custom filters // Register all custom filters

View file

@ -2,6 +2,7 @@
use App\Services\PluginImportService; use App\Services\PluginImportService;
use Livewire\Volt\Component; use Livewire\Volt\Component;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -26,21 +27,40 @@ new class extends Component {
$catalogContent = $response->body(); $catalogContent = $response->body();
$catalog = Yaml::parse($catalogContent); $catalog = Yaml::parse($catalogContent);
return collect($catalog)->map(function ($plugin, $key) { $currentVersion = config('app.version');
return [
'id' => $key, return collect($catalog)
'name' => $plugin['name'] ?? 'Unknown Plugin', ->filter(function ($plugin) use ($currentVersion) {
'description' => $plugin['author_bio']['description'] ?? '', // Check if Laravel compatibility is true
'author' => $plugin['author']['name'] ?? 'Unknown Author', if (!Arr::get($plugin, 'byos.byos_laravel.compatibility', false)) {
'github' => $plugin['author']['github'] ?? null, return false;
'license' => $plugin['license'] ?? null, }
'zip_url' => $plugin['trmnlp']['zip_url'] ?? null,
'repo_url' => $plugin['trmnlp']['repo'] ?? null, // Check minimum version if specified
'logo_url' => $plugin['logo_url'] ?? null, $minVersion = Arr::get($plugin, 'byos.byos_laravel.min_version');
'screenshot_url' => $plugin['screenshot_url'] ?? null, if ($minVersion && $currentVersion && version_compare($currentVersion, $minVersion, '<')) {
'learn_more_url' => $plugin['author_bio']['learn_more_url'] ?? null, return false;
]; }
})->toArray();
return true;
})
->map(function ($plugin, $key) {
return [
'id' => $key,
'name' => Arr::get($plugin, 'name', 'Unknown Plugin'),
'description' => Arr::get($plugin, 'author_bio.description', ''),
'author' => Arr::get($plugin, 'author.name', 'Unknown Author'),
'github' => Arr::get($plugin, 'author.github'),
'license' => Arr::get($plugin, 'license'),
'zip_url' => Arr::get($plugin, 'trmnlp.zip_url'),
'repo_url' => Arr::get($plugin, 'trmnlp.repo'),
'logo_url' => Arr::get($plugin, 'logo_url'),
'screenshot_url' => Arr::get($plugin, 'screenshot_url'),
'learn_more_url' => Arr::get($plugin, 'author_bio.learn_more_url'),
];
})
->sortBy('name')
->toArray();
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Failed to load catalog from URL: ' . $e->getMessage()); Log::error('Failed to load catalog from URL: ' . $e->getMessage());
return []; return [];

View file

@ -38,10 +38,7 @@ new class extends Component {
public function refreshPlugins(): void public function refreshPlugins(): void
{ {
$userPlugins = auth()->user()?->plugins?->map(function ($plugin) { $userPlugins = auth()->user()?->plugins?->makeHidden(['render_markup', 'data_payload'])->toArray();
return $plugin->toArray();
})->toArray();
$this->plugins = array_merge($this->native_plugins, $userPlugins ?? []); $this->plugins = array_merge($this->native_plugins, $userPlugins ?? []);
} }

View file

@ -133,7 +133,7 @@ new class extends Component {
$this->addError('polling_url', 'The polling URL must be a valid URL after resolving configuration variables.'); $this->addError('polling_url', 'The polling URL must be a valid URL after resolving configuration variables.');
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->addError('polling_url', 'Error resolving Liquid variables: ' . $e->getMessage()); $this->addError('polling_url', 'Error resolving Liquid variables: ' . $e->getMessage() . $e->getPrevious()?->getMessage());
} }
} }
} }
@ -148,7 +148,7 @@ new class extends Component {
$this->data_payload_updated_at = $this->plugin->data_payload_updated_at; $this->data_payload_updated_at = $this->plugin->data_payload_updated_at;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->dispatch('data-update-error', message: $e->getMessage()); $this->dispatch('data-update-error', message: $e->getMessage() . $e->getPrevious()?->getMessage());
} }
} }
} }
@ -690,7 +690,10 @@ HTML;
<flux:checkbox label="{{ $label }}" value="{{ $value }}"/> <flux:checkbox label="{{ $label }}" value="{{ $value }}"/>
@endforeach @endforeach
@else @else
<flux:checkbox label="{{ $option }}" value="{{ $option }}"/> @php
$key = mb_strtolower(str_replace(' ', '_', $option));
@endphp
<flux:checkbox label="{{ $option }}" value="{{ $key }}"/>
@endif @endif
@endforeach @endforeach
@endif @endif

View file

@ -38,6 +38,11 @@ it('loads plugins from catalog URL', function () {
'trmnlp' => [ 'trmnlp' => [
'zip_url' => 'https://example.com/plugin.zip', 'zip_url' => 'https://example.com/plugin.zip',
], ],
'byos' => [
'byos_laravel' => [
'compatibility' => true,
]
],
'logo_url' => 'https://example.com/logo.png', 'logo_url' => 'https://example.com/logo.png',
], ],
]; ];

View file

@ -2,7 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
use App\Liquid\Filters\StandardFilters;
use App\Models\Plugin; use App\Models\Plugin;
use Keepsuit\Liquid\Environment;
/** /**
* Tests for the Liquid where filter functionality * Tests for the Liquid where filter functionality
@ -92,3 +94,31 @@ LIQUID
// Should not contain the low tide data // Should not contain the low tide data
$this->assertStringNotContainsString('"type":"L"', $result); $this->assertStringNotContainsString('"type":"L"', $result);
}); });
it('encodes arrays for url_encode as JSON with spaces after commas and then percent-encodes', function () {
/** @var Environment $env */
$env = app('liquid.environment');
$env->filterRegistry->register(StandardFilters::class);
$template = $env->parseString('{{ categories | url_encode }}');
$output = $template->render($env->newRenderContext([
'categories' => ['common', 'obscure'],
]));
expect($output)->toBe('%5B%22common%22%2C%22obscure%22%5D');
});
it('keeps scalar url_encode behavior intact', function () {
/** @var Environment $env */
$env = app('liquid.environment');
$env->filterRegistry->register(StandardFilters::class);
$template = $env->parseString('{{ text | url_encode }}');
$output = $template->render($env->newRenderContext([
'text' => 'hello world',
]));
expect($output)->toBe('hello+world');
});