mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
feat: recipes zip import support, add trmnlp compatible recipe configuration
Some checks are pending
tests / ci (push) Waiting to run
Some checks are pending
tests / ci (push) Waiting to run
* recipes zip import support * add trmnlp compatible recipe configuration * support for multiple polling urls
This commit is contained in:
parent
a927c0fb97
commit
414ca47cbf
17 changed files with 2409 additions and 125 deletions
61
app/Liquid/FileSystems/InlineTemplatesFileSystem.php
Normal file
61
app/Liquid/FileSystems/InlineTemplatesFileSystem.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Liquid\FileSystems;
|
||||
|
||||
use Keepsuit\Liquid\Contracts\LiquidFileSystem;
|
||||
|
||||
/**
|
||||
* A file system that allows registering inline templates defined with the template tag
|
||||
*/
|
||||
class InlineTemplatesFileSystem implements LiquidFileSystem
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $templates = [];
|
||||
|
||||
/**
|
||||
* Register a template with the given name and content
|
||||
*/
|
||||
public function register(string $name, string $content): void
|
||||
{
|
||||
$this->templates[$name] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a template exists
|
||||
*/
|
||||
public function hasTemplate(string $templateName): bool
|
||||
{
|
||||
return isset($this->templates[$templateName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered template names
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getTemplateNames(): array
|
||||
{
|
||||
return array_keys($this->templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all registered templates
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->templates = [];
|
||||
}
|
||||
|
||||
public function readTemplateFile(string $templateName): string
|
||||
{
|
||||
if (!isset($this->templates[$templateName])) {
|
||||
throw new \InvalidArgumentException("Template '{$templateName}' not found in inline templates");
|
||||
}
|
||||
|
||||
return $this->templates[$templateName];
|
||||
}
|
||||
}
|
||||
99
app/Liquid/Tags/TemplateTag.php
Normal file
99
app/Liquid/Tags/TemplateTag.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Liquid\Tags;
|
||||
|
||||
use App\Liquid\FileSystems\InlineTemplatesFileSystem;
|
||||
use Keepsuit\Liquid\Exceptions\SyntaxException;
|
||||
use Keepsuit\Liquid\Nodes\BodyNode;
|
||||
use Keepsuit\Liquid\Nodes\Raw;
|
||||
use Keepsuit\Liquid\Nodes\VariableLookup;
|
||||
use Keepsuit\Liquid\Parse\TagParseContext;
|
||||
use Keepsuit\Liquid\Render\RenderContext;
|
||||
use Keepsuit\Liquid\TagBlock;
|
||||
|
||||
/**
|
||||
* The {% template [name] %} tag block is used to define custom templates within the context of the current Liquid template.
|
||||
* These templates are registered with the InlineTemplatesFileSystem and can be rendered using the render tag.
|
||||
*/
|
||||
class TemplateTag extends TagBlock
|
||||
{
|
||||
protected string $templateName;
|
||||
protected Raw $body;
|
||||
|
||||
public static function tagName(): string
|
||||
{
|
||||
return 'template';
|
||||
}
|
||||
|
||||
public static function hasRawBody(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function parse(TagParseContext $context): static
|
||||
{
|
||||
// Get the template name from the tag parameters
|
||||
$templateNameExpression = $context->params->expression();
|
||||
|
||||
$this->templateName = match (true) {
|
||||
is_string($templateNameExpression) => trim($templateNameExpression),
|
||||
is_numeric($templateNameExpression) => (string) $templateNameExpression,
|
||||
$templateNameExpression instanceof VariableLookup => (string) $templateNameExpression,
|
||||
default => throw new SyntaxException("Template name must be a string, number, or variable"),
|
||||
};
|
||||
|
||||
// Validate template name (letters, numbers, underscores, and slashes only)
|
||||
if (!preg_match('/^[a-zA-Z0-9_\/]+$/', $this->templateName)) {
|
||||
throw new SyntaxException("Invalid template name '{$this->templateName}' - template names must contain only letters, numbers, underscores, and slashes");
|
||||
}
|
||||
|
||||
$context->params->assertEnd();
|
||||
|
||||
assert($context->body instanceof BodyNode);
|
||||
|
||||
$body = $context->body->children()[0] ?? null;
|
||||
$this->body = match (true) {
|
||||
$body instanceof Raw => $body,
|
||||
default => throw new SyntaxException('template tag must have a single raw body'),
|
||||
};
|
||||
|
||||
// Register the template with the file system during parsing
|
||||
$fileSystem = $context->getParseContext()->environment->fileSystem;
|
||||
if ($fileSystem instanceof InlineTemplatesFileSystem) {
|
||||
// Store the raw content for later rendering
|
||||
$fileSystem->register($this->templateName, $this->body->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(RenderContext $context): string
|
||||
{
|
||||
// Get the file system from the environment
|
||||
$fileSystem = $context->environment->fileSystem;
|
||||
|
||||
if (!$fileSystem instanceof InlineTemplatesFileSystem) {
|
||||
// If no inline file system is available, just return empty string
|
||||
// This allows the template to be used in contexts where inline templates aren't supported
|
||||
return '';
|
||||
}
|
||||
|
||||
// Register the template with the file system
|
||||
$fileSystem->register($this->templateName, $this->body->render($context));
|
||||
|
||||
// Return empty string as template tags don't output anything
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTemplateName(): string
|
||||
{
|
||||
return $this->templateName;
|
||||
}
|
||||
|
||||
public function getBody(): Raw
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,23 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Liquid\FileSystems\InlineTemplatesFileSystem;
|
||||
use App\Liquid\Filters\Data;
|
||||
use App\Liquid\Filters\Localization;
|
||||
use App\Liquid\Filters\Numbers;
|
||||
use App\Liquid\Filters\StringMarkup;
|
||||
use App\Liquid\Filters\Uniqueness;
|
||||
use App\Liquid\Tags\TemplateTag;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Keepsuit\Liquid\Exceptions\LiquidException;
|
||||
use Keepsuit\Liquid\Extensions\StandardExtension;
|
||||
|
||||
class Plugin extends Model
|
||||
{
|
||||
|
|
@ -26,6 +31,8 @@ class Plugin extends Model
|
|||
'data_payload_updated_at' => 'datetime',
|
||||
'is_native' => 'boolean',
|
||||
'markup_language' => 'string',
|
||||
'configuration' => 'json',
|
||||
'configuration_template' => 'json',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
|
|
@ -39,6 +46,49 @@ class Plugin extends Model
|
|||
});
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function hasMissingRequiredConfigurationFields(): bool
|
||||
{
|
||||
if (! isset($this->configuration_template['custom_fields']) || empty($this->configuration_template['custom_fields'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->configuration_template['custom_fields'] as $field) {
|
||||
// Skip fields as they are informational only
|
||||
if ($field['field_type'] === 'author_bio') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field['field_type'] === 'copyable') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field['field_type'] === 'copyable_webhook_url') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldKey = $field['keyname'] ?? $field['key'] ?? $field['name'];
|
||||
|
||||
// Check if field is required (not marked as optional)
|
||||
$isRequired = ! isset($field['optional']) || $field['optional'] !== true;
|
||||
|
||||
if ($isRequired) {
|
||||
$currentValue = $this->configuration[$fieldKey] ?? null;
|
||||
|
||||
// If the field has a default value and no current value is set, it's not missing
|
||||
if (($currentValue === null || $currentValue === '' || (is_array($currentValue) && empty($currentValue))) && ! isset($field['default'])) {
|
||||
return true; // Found a required field that is not set and has no default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // All required fields are set
|
||||
}
|
||||
|
||||
public function isDataStale(): bool
|
||||
{
|
||||
if ($this->data_strategy === 'webhook') {
|
||||
|
|
@ -59,7 +109,9 @@ class Plugin extends Model
|
|||
$headers = ['User-Agent' => 'usetrmnl/byos_laravel', 'Accept' => 'application/json'];
|
||||
|
||||
if ($this->polling_header) {
|
||||
$headerLines = explode("\n", trim($this->polling_header));
|
||||
// Resolve Liquid variables in the polling header
|
||||
$resolvedHeader = $this->resolveLiquidVariables($this->polling_header);
|
||||
$headerLines = explode("\n", trim($resolvedHeader));
|
||||
foreach ($headerLines as $line) {
|
||||
$parts = explode(':', $line, 2);
|
||||
if (count($parts) === 2) {
|
||||
|
|
@ -68,26 +120,138 @@ class Plugin extends Model
|
|||
}
|
||||
}
|
||||
|
||||
$httpRequest = Http::withHeaders($headers);
|
||||
// Split URLs by newline and filter out empty lines
|
||||
$urls = array_filter(
|
||||
array_map('trim', explode("\n", $this->polling_url)),
|
||||
fn ($url) => ! empty($url)
|
||||
);
|
||||
|
||||
if ($this->polling_verb === 'post' && $this->polling_body) {
|
||||
$httpRequest = $httpRequest->withBody($this->polling_body);
|
||||
// If only one URL, use the original logic without nesting
|
||||
if (count($urls) === 1) {
|
||||
$url = reset($urls);
|
||||
$httpRequest = Http::withHeaders($headers);
|
||||
|
||||
if ($this->polling_verb === 'post' && $this->polling_body) {
|
||||
// Resolve Liquid variables in the polling body
|
||||
$resolvedBody = $this->resolveLiquidVariables($this->polling_body);
|
||||
$httpRequest = $httpRequest->withBody($resolvedBody);
|
||||
}
|
||||
|
||||
// Resolve Liquid variables in the polling URL
|
||||
$resolvedUrl = $this->resolveLiquidVariables($url);
|
||||
|
||||
try {
|
||||
// Make the request based on the verb
|
||||
if ($this->polling_verb === 'post') {
|
||||
$response = $httpRequest->post($resolvedUrl)->json();
|
||||
} else {
|
||||
$response = $httpRequest->get($resolvedUrl)->json();
|
||||
}
|
||||
|
||||
$this->update([
|
||||
'data_payload' => $response,
|
||||
'data_payload_updated_at' => now(),
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Log::warning("Failed to fetch data from URL {$resolvedUrl}: ".$e->getMessage());
|
||||
$this->update([
|
||||
'data_payload' => ['error' => 'Failed to fetch data'],
|
||||
'data_payload_updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make the request based on the verb
|
||||
if ($this->polling_verb === 'post') {
|
||||
$response = $httpRequest->post($this->polling_url)->json();
|
||||
} else {
|
||||
$response = $httpRequest->get($this->polling_url)->json();
|
||||
// Multiple URLs - use nested response logic
|
||||
$combinedResponse = [];
|
||||
|
||||
foreach ($urls as $index => $url) {
|
||||
$httpRequest = Http::withHeaders($headers);
|
||||
|
||||
if ($this->polling_verb === 'post' && $this->polling_body) {
|
||||
// Resolve Liquid variables in the polling body
|
||||
$resolvedBody = $this->resolveLiquidVariables($this->polling_body);
|
||||
$httpRequest = $httpRequest->withBody($resolvedBody);
|
||||
}
|
||||
|
||||
// Resolve Liquid variables in the polling URL
|
||||
$resolvedUrl = $this->resolveLiquidVariables($url);
|
||||
|
||||
try {
|
||||
// Make the request based on the verb
|
||||
if ($this->polling_verb === 'post') {
|
||||
$response = $httpRequest->post($resolvedUrl)->json();
|
||||
} else {
|
||||
$response = $httpRequest->get($resolvedUrl)->json();
|
||||
}
|
||||
|
||||
// Check if response is an array at root level
|
||||
if (is_array($response) && array_keys($response) === range(0, count($response) - 1)) {
|
||||
// Response is a sequential array, nest under .data
|
||||
$combinedResponse["IDX_{$index}"] = ['data' => $response];
|
||||
} else {
|
||||
// Response is an object or associative array, keep as is
|
||||
$combinedResponse["IDX_{$index}"] = $response;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Log error and continue with other URLs
|
||||
Log::warning("Failed to fetch data from URL {$resolvedUrl}: ".$e->getMessage());
|
||||
$combinedResponse["IDX_{$index}"] = ['error' => 'Failed to fetch data'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->update([
|
||||
'data_payload' => $response,
|
||||
'data_payload' => $combinedResponse,
|
||||
'data_payload_updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Liquid template replacements (converts 'with' syntax to comma syntax)
|
||||
*/
|
||||
private function applyLiquidReplacements(string $template): string
|
||||
{
|
||||
$replacements = [
|
||||
'date: "%N"' => 'date: "u"',
|
||||
'%-m/%-d/%Y' => 'm/d/Y',
|
||||
];
|
||||
|
||||
// Apply basic replacements
|
||||
$template = str_replace(array_keys($replacements), array_values($replacements), $template);
|
||||
|
||||
// Convert {% render "template" with %} syntax to {% render "template", %} syntax
|
||||
$template = preg_replace(
|
||||
'/{%\s*render\s+([^}]+?)\s+with\s+/i',
|
||||
'{% render $1, ',
|
||||
$template
|
||||
);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve Liquid variables in a template string using the Liquid template engine
|
||||
*
|
||||
* @param string $template The template string containing Liquid variables
|
||||
* @return string The resolved template with variables replaced with their values
|
||||
*
|
||||
* @throws LiquidException
|
||||
*/
|
||||
public function resolveLiquidVariables(string $template): string
|
||||
{
|
||||
// Get configuration variables - make them available at root level
|
||||
$variables = $this->configuration ?? [];
|
||||
|
||||
// Use the Liquid template engine to resolve variables
|
||||
$environment = App::make('liquid.environment');
|
||||
$liquidTemplate = $environment->parseString($template);
|
||||
$context = $environment->newRenderContext(data: $variables);
|
||||
|
||||
return $liquidTemplate->render($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the plugin's markup
|
||||
*
|
||||
|
|
@ -99,7 +263,12 @@ class Plugin extends Model
|
|||
$renderedContent = '';
|
||||
|
||||
if ($this->markup_language === 'liquid') {
|
||||
$environment = App::make('liquid.environment');
|
||||
// Create a custom environment with inline templates support
|
||||
$inlineFileSystem = new InlineTemplatesFileSystem();
|
||||
$environment = new \Keepsuit\Liquid\Environment(
|
||||
fileSystem: $inlineFileSystem,
|
||||
extensions: [new StandardExtension()]
|
||||
);
|
||||
|
||||
// Register all custom filters
|
||||
$environment->filterRegistry->register(Numbers::class);
|
||||
|
|
@ -108,11 +277,47 @@ class Plugin extends Model
|
|||
$environment->filterRegistry->register(Uniqueness::class);
|
||||
$environment->filterRegistry->register(Localization::class);
|
||||
|
||||
$template = $environment->parseString($this->render_markup);
|
||||
$context = $environment->newRenderContext(data: ['size' => $size, 'data' => $this->data_payload]);
|
||||
// Register the template tag for inline templates
|
||||
$environment->tagRegistry->register(TemplateTag::class);
|
||||
|
||||
// Apply Liquid replacements (including 'with' syntax conversion)
|
||||
$processedMarkup = $this->applyLiquidReplacements($this->render_markup);
|
||||
|
||||
$template = $environment->parseString($processedMarkup);
|
||||
$context = $environment->newRenderContext(
|
||||
data: [
|
||||
'size' => $size,
|
||||
'data' => $this->data_payload,
|
||||
'config' => $this->configuration ?? [],
|
||||
...(is_array($this->data_payload) ? $this->data_payload : []),
|
||||
'trmnl' => [
|
||||
'user' => [
|
||||
'utc_offset' => '0',
|
||||
'name' => $this->user->name ?? 'Unknown User',
|
||||
'locale' => 'en',
|
||||
'time_zone_iana' => config('app.timezone'),
|
||||
],
|
||||
'plugin_settings' => [
|
||||
'instance_name' => $this->name,
|
||||
'strategy' => $this->data_strategy,
|
||||
'dark_mode' => 'no',
|
||||
'no_screen_padding' => 'no',
|
||||
'polling_headers' => $this->polling_header,
|
||||
'polling_url' => $this->polling_url,
|
||||
'custom_fields_values' => [
|
||||
...(is_array($this->configuration) ? $this->configuration : []),
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$renderedContent = $template->render($context);
|
||||
} else {
|
||||
$renderedContent = Blade::render($this->render_markup, ['size' => $size, 'data' => $this->data_payload]);
|
||||
$renderedContent = Blade::render($this->render_markup, [
|
||||
'size' => $size,
|
||||
'data' => $this->data_payload,
|
||||
'config' => $this->configuration ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($standalone) {
|
||||
|
|
@ -130,6 +335,7 @@ class Plugin extends Model
|
|||
'slot' => view($this->render_markup_view, [
|
||||
'size' => $size,
|
||||
'data' => $this->data_payload,
|
||||
'config' => $this->configuration ?? [],
|
||||
])->render(),
|
||||
])->render();
|
||||
}
|
||||
|
|
@ -137,10 +343,19 @@ class Plugin extends Model
|
|||
return view($this->render_markup_view, [
|
||||
'size' => $size,
|
||||
'data' => $this->data_payload,
|
||||
'config' => $this->configuration ?? [],
|
||||
])->render();
|
||||
|
||||
}
|
||||
|
||||
return '<p>No render markup yet defined for this plugin.</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration value by key
|
||||
*/
|
||||
public function getConfiguration(string $key, $default = null)
|
||||
{
|
||||
return $this->configuration[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Services\OidcProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
|
@ -26,6 +27,17 @@ class AppServiceProvider extends ServiceProvider
|
|||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
Request::macro('hasValidSignature', function ($absolute = true, array $ignoreQuery = []) {
|
||||
$https = clone $this;
|
||||
$https->server->set('HTTPS', 'on');
|
||||
|
||||
$http = clone $this;
|
||||
$http->server->set('HTTPS', 'off');
|
||||
|
||||
return URL::hasValidSignature($https, $absolute, $ignoreQuery)
|
||||
|| URL::hasValidSignature($http, $absolute, $ignoreQuery);
|
||||
});
|
||||
|
||||
// Register OIDC provider with Socialite
|
||||
Socialite::extend('oidc', function ($app) {
|
||||
$config = $app['config']['services.oidc'] ?? [];
|
||||
|
|
|
|||
206
app/Services/PluginImportService.php
Normal file
206
app/Services/PluginImportService.php
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use ZipArchive;
|
||||
|
||||
class PluginImportService
|
||||
{
|
||||
/**
|
||||
* Import a plugin from a ZIP file
|
||||
*
|
||||
* @param UploadedFile $zipFile The uploaded ZIP file
|
||||
* @param User $user The user importing the plugin
|
||||
* @return Plugin The created plugin instance
|
||||
*
|
||||
* @throws Exception If the ZIP file is invalid or required files are missing
|
||||
*/
|
||||
public function importFromZip(UploadedFile $zipFile, User $user): Plugin
|
||||
{
|
||||
// Create a temporary directory using Laravel's temporary directory helper
|
||||
$tempDirName = 'temp/'.uniqid('plugin_import_', true);
|
||||
Storage::makeDirectory($tempDirName);
|
||||
$tempDir = Storage::path($tempDirName);
|
||||
|
||||
try {
|
||||
// Get the real path of the temporary file
|
||||
$zipFullPath = $zipFile->getRealPath();
|
||||
|
||||
// Extract the ZIP file using ZipArchive
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipFullPath) !== true) {
|
||||
throw new Exception('Could not open the ZIP file.');
|
||||
}
|
||||
|
||||
$zip->extractTo($tempDir);
|
||||
$zip->close();
|
||||
|
||||
// Find the required files (settings.yml and full.liquid/full.blade.php)
|
||||
$filePaths = $this->findRequiredFiles($tempDir);
|
||||
|
||||
// Validate that we found the required files
|
||||
if (! $filePaths['settingsYamlPath'] || ! $filePaths['fullLiquidPath']) {
|
||||
throw new Exception('Invalid ZIP structure. Required files settings.yml and full.liquid/full.blade.php are missing.');
|
||||
}
|
||||
|
||||
// Parse settings.yml
|
||||
$settingsYaml = File::get($filePaths['settingsYamlPath']);
|
||||
$settings = Yaml::parse($settingsYaml);
|
||||
|
||||
// Read full.liquid content
|
||||
$fullLiquid = File::get($filePaths['fullLiquidPath']);
|
||||
$fullLiquid = '<div class="view view--{{ size }}">'."\n".$fullLiquid."\n".'</div>';
|
||||
|
||||
// Prepend shared.liquid content if available
|
||||
if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
|
||||
$sharedLiquid = File::get($filePaths['sharedLiquidPath']);
|
||||
$fullLiquid = $sharedLiquid."\n".$fullLiquid;
|
||||
}
|
||||
|
||||
// Check if the file ends with .liquid to set markup language
|
||||
$markupLanguage = 'blade';
|
||||
if (pathinfo($filePaths['fullLiquidPath'], PATHINFO_EXTENSION) === 'liquid') {
|
||||
$markupLanguage = 'liquid';
|
||||
}
|
||||
|
||||
// Ensure custom_fields is properly formatted
|
||||
if (! isset($settings['custom_fields']) || ! is_array($settings['custom_fields'])) {
|
||||
$settings['custom_fields'] = [];
|
||||
}
|
||||
|
||||
// Create configuration template with the custom fields
|
||||
$configurationTemplate = [
|
||||
'custom_fields' => $settings['custom_fields'],
|
||||
];
|
||||
|
||||
// Extract default values from custom_fields and populate configuration
|
||||
$configuration = [];
|
||||
if (isset($settings['custom_fields']) && is_array($settings['custom_fields'])) {
|
||||
foreach ($settings['custom_fields'] as $field) {
|
||||
if (isset($field['keyname']) && isset($field['default'])) {
|
||||
$configuration[$field['keyname']] = $field['default'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new plugin
|
||||
$plugin = Plugin::create([
|
||||
'user_id' => $user->id,
|
||||
'name' => $settings['name'] ?? 'Imported Plugin',
|
||||
'data_stale_minutes' => $settings['refresh_interval'] ?? 15,
|
||||
'data_strategy' => $settings['strategy'] ?? 'static',
|
||||
'polling_url' => $settings['polling_url'] ?? null,
|
||||
'polling_verb' => $settings['polling_verb'] ?? 'get',
|
||||
'polling_header' => isset($settings['polling_headers'])
|
||||
? str_replace('=', ':', $settings['polling_headers'])
|
||||
: null,
|
||||
'polling_body' => $settings['polling_body'] ?? null,
|
||||
'markup_language' => $markupLanguage,
|
||||
'render_markup' => $fullLiquid,
|
||||
'configuration_template' => $configurationTemplate,
|
||||
'configuration' => $configuration,
|
||||
'data_payload' => json_decode($settings['static_data'] ?? '{}', true),
|
||||
]);
|
||||
|
||||
return $plugin;
|
||||
|
||||
} finally {
|
||||
// Clean up temporary directory
|
||||
Storage::deleteDirectory($tempDirName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find required files in the extracted ZIP directory
|
||||
*
|
||||
* @param string $tempDir The temporary directory path
|
||||
* @return array Array containing paths to required files
|
||||
*/
|
||||
private function findRequiredFiles(string $tempDir): array
|
||||
{
|
||||
$settingsYamlPath = null;
|
||||
$fullLiquidPath = null;
|
||||
$sharedLiquidPath = null;
|
||||
|
||||
// First, check if files are directly in the src folder
|
||||
if (File::exists($tempDir.'/src/settings.yml')) {
|
||||
$settingsYamlPath = $tempDir.'/src/settings.yml';
|
||||
|
||||
// Check for full.liquid or full.blade.php
|
||||
if (File::exists($tempDir.'/src/full.liquid')) {
|
||||
$fullLiquidPath = $tempDir.'/src/full.liquid';
|
||||
} elseif (File::exists($tempDir.'/src/full.blade.php')) {
|
||||
$fullLiquidPath = $tempDir.'/src/full.blade.php';
|
||||
}
|
||||
|
||||
// Check for shared.liquid in the same directory
|
||||
if (File::exists($tempDir.'/src/shared.liquid')) {
|
||||
$sharedLiquidPath = $tempDir.'/src/shared.liquid';
|
||||
}
|
||||
} else {
|
||||
// Search for the files in the extracted directory structure
|
||||
$directories = new RecursiveDirectoryIterator($tempDir, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$files = new RecursiveIteratorIterator($directories);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = $file->getFilename();
|
||||
$filepath = $file->getPathname();
|
||||
|
||||
if ($filename === 'settings.yml') {
|
||||
$settingsYamlPath = $filepath;
|
||||
} elseif ($filename === 'full.liquid' || $filename === 'full.blade.php') {
|
||||
$fullLiquidPath = $filepath;
|
||||
} elseif ($filename === 'shared.liquid') {
|
||||
$sharedLiquidPath = $filepath;
|
||||
}
|
||||
|
||||
// If we found both required files, break the loop
|
||||
if ($settingsYamlPath && $fullLiquidPath) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found the files but they're not in the src folder,
|
||||
// check if they're in the root of the ZIP or in a subfolder
|
||||
if ($settingsYamlPath && $fullLiquidPath) {
|
||||
// If the files are in the root of the ZIP, create a src folder and move them there
|
||||
$srcDir = dirname($settingsYamlPath);
|
||||
|
||||
// If the parent directory is not named 'src', create a src directory
|
||||
if (basename($srcDir) !== 'src') {
|
||||
$newSrcDir = $tempDir.'/src';
|
||||
File::makeDirectory($newSrcDir, 0755, true);
|
||||
|
||||
// Copy the files to the src directory
|
||||
File::copy($settingsYamlPath, $newSrcDir.'/settings.yml');
|
||||
File::copy($fullLiquidPath, $newSrcDir.'/full.liquid');
|
||||
|
||||
// Copy shared.liquid if it exists
|
||||
if ($sharedLiquidPath) {
|
||||
File::copy($sharedLiquidPath, $newSrcDir.'/shared.liquid');
|
||||
$sharedLiquidPath = $newSrcDir.'/shared.liquid';
|
||||
}
|
||||
|
||||
// Update the paths
|
||||
$settingsYamlPath = $newSrcDir.'/settings.yml';
|
||||
$fullLiquidPath = $newSrcDir.'/full.liquid';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'settingsYamlPath' => $settingsYamlPath,
|
||||
'fullLiquidPath' => $fullLiquidPath,
|
||||
'sharedLiquidPath' => $sharedLiquidPath,
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue