byos_laravel/app/Services/PluginExportService.php
2026-02-06 22:55:08 +01:00

182 lines
6.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Plugin;
use App\Models\User;
use Exception;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\Yaml\Yaml;
use ZipArchive;
/**
* PluginExportService
*
* Exports plugins to ZIP files in the same format that can be imported by PluginImportService.
*
* The exported ZIP file contains:
* - settings.yml: Plugin configuration including custom fields, polling settings, etc.
* - full.liquid or full.blade.php: The main template file
* - shared.liquid: Optional shared template (for liquid templates)
*
* This format is compatible with the PluginImportService and can be used to:
* - Backup plugins
* - Share plugins between users
* - Migrate plugins between environments
* - Create plugin templates
*/
class PluginExportService
{
/**
* Export a plugin to a ZIP file in the same format that can be imported
*
* @param Plugin $plugin The plugin to export
* @param User $user The user exporting the plugin
* @return BinaryFileResponse The ZIP file response
*
* @throws Exception If the ZIP file cannot be created
*/
public function exportToZip(Plugin $plugin, User $user): BinaryFileResponse
{
// Create a temporary directory
$tempDirName = 'temp/'.uniqid('plugin_export_', true);
Storage::makeDirectory($tempDirName);
$tempDir = Storage::path($tempDirName);
// Generate settings.yml content
$settings = $this->generateSettingsYaml($plugin);
$settingsYaml = Yaml::dump($settings, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
File::put($tempDir.'/settings.yml', $settingsYaml);
$extension = $plugin->markup_language === 'liquid' ? 'liquid' : 'blade.php';
// Export full template if it exists
if ($plugin->render_markup) {
$fullTemplate = $this->generateLayoutTemplate($plugin->render_markup);
File::put($tempDir.'/full.'.$extension, $fullTemplate);
}
// Export layout-specific templates if they exist
if ($plugin->render_markup_half_horizontal) {
$halfHorizontalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_horizontal);
File::put($tempDir.'/half_horizontal.'.$extension, $halfHorizontalTemplate);
}
if ($plugin->render_markup_half_vertical) {
$halfVerticalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_vertical);
File::put($tempDir.'/half_vertical.'.$extension, $halfVerticalTemplate);
}
if ($plugin->render_markup_quadrant) {
$quadrantTemplate = $this->generateLayoutTemplate($plugin->render_markup_quadrant);
File::put($tempDir.'/quadrant.'.$extension, $quadrantTemplate);
}
// Export shared template if it exists
if ($plugin->render_markup_shared) {
$sharedTemplate = $this->generateLayoutTemplate($plugin->render_markup_shared);
File::put($tempDir.'/shared.'.$extension, $sharedTemplate);
}
// Create ZIP file
$zipPath = $tempDir.'/plugin_'.$plugin->trmnlp_id.'.zip';
$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE) !== true) {
throw new Exception('Could not create ZIP file.');
}
// Add files directly to ZIP root
$this->addDirectoryToZip($zip, $tempDir, '');
$zip->close();
// Return the ZIP file as a download response
return response()->download($zipPath, 'plugin_'.$plugin->trmnlp_id.'.zip');
}
/**
* Generate the settings.yml content for the plugin
*/
private function generateSettingsYaml(Plugin $plugin): array
{
$settings = [];
// Add fields in the specific order requested
$settings['name'] = $plugin->name;
$settings['no_screen_padding'] = 'no'; // Default value
$settings['dark_mode'] = 'no'; // Default value
$settings['strategy'] = $plugin->data_strategy;
// Add static data if available
if ($plugin->data_payload) {
$settings['static_data'] = json_encode($plugin->data_payload, JSON_PRETTY_PRINT);
}
// Add polling configuration if applicable
if ($plugin->data_strategy === 'polling') {
if ($plugin->polling_verb) {
$settings['polling_verb'] = $plugin->polling_verb;
}
if ($plugin->polling_url) {
$settings['polling_url'] = $plugin->polling_url;
}
if ($plugin->polling_header) {
// Convert header format from "key: value" to "key=value"
$settings['polling_headers'] = str_replace(':', '=', $plugin->polling_header);
}
if ($plugin->polling_body) {
$settings['polling_body'] = $plugin->polling_body;
}
}
$settings['refresh_interval'] = $plugin->data_stale_minutes;
$settings['id'] = $plugin->trmnlp_id;
// Add custom fields from configuration template
if (isset($plugin->configuration_template['custom_fields'])) {
$settings['custom_fields'] = $plugin->configuration_template['custom_fields'];
}
return $settings;
}
/**
* Generate template content from markup, removing wrapper divs if present
*/
private function generateLayoutTemplate(?string $markup): string
{
if (! $markup) {
return '';
}
// Remove the wrapper div if it exists (it will be added during import for liquid)
$markup = preg_replace('/^<div class="view view--\{\{ size \}\}">\s*/', '', $markup);
$markup = preg_replace('/\s*<\/div>\s*$/', '', $markup);
return mb_trim($markup);
}
/**
* Add a directory and its contents to a ZIP file
*/
private function addDirectoryToZip(ZipArchive $zip, string $dirPath, string $zipPath): void
{
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dirPath),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (! $file->isDir()) {
$filePath = $file->getRealPath();
$fileName = basename((string) $filePath);
// For root directory, just use the filename
$relativePath = $zipPath === '' ? $fileName : $zipPath.'/'.mb_substr((string) $filePath, mb_strlen($dirPath) + 1);
$zip->addFile($filePath, $relativePath);
}
}
}
}