diff --git a/database/migrations/2024_10_05_create_htmlable_elements_table.php b/database/migrations/2024_10_05_create_htmlable_elements_table.php index 46d7b19..8e81b4f 100644 --- a/database/migrations/2024_10_05_create_htmlable_elements_table.php +++ b/database/migrations/2024_10_05_create_htmlable_elements_table.php @@ -13,7 +13,7 @@ public function up() Schema::create('elements', function (Blueprint $table) { $table->id(); $table->uuid()->unique(); - $table->foreignIdFor(Document::class)->onDelete('cascade'); + $table->foreignIdFor(Document::class)->nullable()->onDelete('cascade'); $table->foreignIdFor(Element::class, 'parent_id')->nullable()->onDelete('cascade'); $table->string('tag'); $table->timestamps(); diff --git a/src/Actions/Documents/Import.php b/src/Actions/Documents/Import.php index 021be6c..c30509a 100644 --- a/src/Actions/Documents/Import.php +++ b/src/Actions/Documents/Import.php @@ -3,9 +3,10 @@ namespace JamesDordoy\HTMLable\Actions\Documents; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Storage; use JamesDordoy\HTMLable\Models\Document; +use Spatie\TemporaryDirectory\TemporaryDirectory; use ZipArchive; +use Illuminate\Support\Facades\File; class Import { @@ -13,85 +14,53 @@ public function __invoke(ZipArchive $zip, Model $model, bool $validate = true): { $document = app(Create::class)($model, pathinfo($zip->filename, PATHINFO_FILENAME), ''); - // Create a unique directory within storage/app/temp for extraction - $extractPath = 'temp/'.uniqid(); - $fullpath = $extractPath.'/'.pathinfo($zip->filename, PATHINFO_FILENAME); - Storage::makeDirectory($extractPath); + $temporaryDirectory = (new TemporaryDirectory())->create(); + $extractPath = $temporaryDirectory->path(); + $fullpath = $extractPath . '/' . pathinfo($zip->filename, PATHINFO_FILENAME); - // Extract the zip contents - $zip->extractTo(Storage::path($extractPath)); + $zip->extractTo($extractPath); $zip->close(); - // Add images in the 'img' folder to the media library - if (Storage::exists($fullpath.'/img')) { - $assetFiles = Storage::files($fullpath.'/img'); + $this->importAssetFolders($document, $fullpath, ['img', 'imgs', 'assets']); - foreach ($assetFiles as $file) { - // Add the file to the media library (assuming these are images) - if (preg_match('/\.(jpg|jpeg|png|gif)$/i', $file)) { - $document->addMedia(Storage::path($file))->toMediaCollection(); - } - } + if (file_exists("{$fullpath}/index.html")) { + $document->compose(file_get_contents("{$fullpath}/index.html")); } - $document->compose(Storage::get($fullpath.'/index.html')); - - Storage::deleteDirectory($extractPath); + $temporaryDirectory->delete(); return $document; } - /** - * Recursive function to move all contents from one directory to another - * using the Storage facade. - */ - protected function moveDirectoryContents($source, $destination) + protected function importAssetFolders(Document $document, string $basePath, array $folders) { - // Get all files and directories in the source folder - $files = Storage::allFiles($source); - $directories = Storage::allDirectories($source); - - // Move all files to the destination folder - foreach ($files as $file) { - $newLocation = str_replace($source, $destination, $file); - Storage::move($file, $newLocation); - } - - // Move all directories to the destination folder - foreach ($directories as $directory) { - $newLocation = str_replace($source, $destination, $directory); - Storage::makeDirectory($newLocation); // Create the new directory in the destination - $this->moveDirectoryContents($directory, $newLocation); // Move the contents recursively - Storage::deleteDirectory($directory); // Remove the now empty source directory + foreach ($folders as $folder) { + $this->importAssets($document, "{$basePath}/{$folder}"); } } - /** - * Function to remove unwanted files and directories (e.g., __MACOSX, .DS_Store) - */ - protected function removeUnwantedFiles($path) + protected function importAssets(Document $document, string $assetPath) { - // Get all files and directories, including hidden ones - $files = Storage::allFiles($path); - $directories = Storage::allDirectories($path); - - // Remove unwanted files - foreach ($files as $file) { - if (preg_match('/(__MACOSX|\.DS_Store)/', $file)) { - Storage::delete($file); // Delete unwanted files - } + if (!file_exists($assetPath)) { + return; } - // Remove unwanted directories - foreach ($directories as $directory) { - if (preg_match('/(__MACOSX)/', $directory)) { - Storage::deleteDirectory($directory); // Delete unwanted directories + $assetFiles = File::allFiles($assetPath); + + foreach ($assetFiles as $file) { + if ($this->isImage($file)) { + $document->addMedia($file->getRealPath())->toMediaCollection(); } } } + protected function isImage(string $file): bool + { + return preg_match('/\.(jpg|jpeg|png|gif)$/i', $file); + } + protected function validate(ZipArchive $zip): array { return []; } -} +} \ No newline at end of file diff --git a/src/Actions/Elements/UpdateInline.php b/src/Actions/Elements/UpdateInline.php index e69de29..eaf53d3 100644 --- a/src/Actions/Elements/UpdateInline.php +++ b/src/Actions/Elements/UpdateInline.php @@ -0,0 +1,8 @@ +middleware(['signed'])->name('htmlable.media.serve-signed'); + Route::get('/htmlable/media/{media}/serve', ServeMediaController::class); } } diff --git a/src/Http/Controllers/Media/ServeMediaController.php b/src/Http/Controllers/Media/ServeMediaController.php new file mode 100644 index 0000000..1bdc692 --- /dev/null +++ b/src/Http/Controllers/Media/ServeMediaController.php @@ -0,0 +1,16 @@ +toInlineResponse($request); + } +} + diff --git a/src/Http/Controllers/Media/ServeMediaSignedController.php b/src/Http/Controllers/Media/ServeMediaSignedController.php new file mode 100644 index 0000000..5dc4da1 --- /dev/null +++ b/src/Http/Controllers/Media/ServeMediaSignedController.php @@ -0,0 +1,16 @@ +toInlineResponse($request); + } +} + diff --git a/src/Models/Document.php b/src/Models/Document.php index fa3e869..f677cb3 100644 --- a/src/Models/Document.php +++ b/src/Models/Document.php @@ -7,12 +7,14 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Support\Facades\URL; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; use JamesDordoy\HTMLable\Actions\Values\Create as CreateValue; use JamesDordoy\HTMLable\Enums\HtmlElement; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; +use Spatie\MediaLibrary\MediaCollections\Models\Media; class Document extends Model implements HasMedia, Renderable { @@ -35,27 +37,7 @@ public function render(): HtmlString $rootElement = $this->elements()->whereNull('parent_id')->first(); $docType = $this->doctype ?? ''; - return new HtmlString("{$docType}{$this->renderElement($rootElement)}"); - } - - protected function renderElement(Element $element): string - { - $tag = $element->tag; - $attributes = $element->values - ->map(fn ($value) => "{$value->key}=\"{$value->value}\"") - ->implode(' '); - - if (in_array($tag, HtmlElement::getSelfClosingElements())) { - return "<{$tag} {$attributes} />\n"; - } - - $content = $element->values->firstWhere('key', 'content')->value ?? ''; - - $childrenHtml = $element - ->children - ->map(fn ($child) => $this->renderElement($child))->implode(''); - - return "<{$tag} {$attributes}>{$content}{$childrenHtml}\n"; + return new HtmlString("{$docType}{$rootElement->render()}"); } /** @@ -101,34 +83,58 @@ protected function saveElement($node, $parentId = null) // element: $element // ); - Value::create([ - 'element_id' => $element->id, - 'key' => $attr->nodeName, - 'value' => $attr->nodeValue, - ]); + + if ($node->nodeName === 'img' && $attr->nodeName === 'src') { + Value::create([ + 'element_id' => $element->id, + 'key' => $attr->nodeName, + 'value' => $this->getSignedPath($attr->nodeValue), + ]); + } else { + Value::create([ + 'element_id' => $element->id, + 'key' => $attr->nodeName, + 'value' => $attr->nodeValue, + ]); + } } if (in_array($node->nodeName, HtmlElement::getSelfClosingElements())) { return; } - // Recursively process child elements - foreach ($node->childNodes as $childNode) { - if ($childNode->nodeType === XML_TEXT_NODE && ! empty(trim($childNode->nodeValue))) { - app(CreateValue::class)( - key: 'content', - value: trim($node->nodeValue), - element: $element - ); - } elseif ($childNode->nodeType === XML_ELEMENT_NODE) { - // Recursively process child elements - $this->saveElement($childNode, $element->id); // Recursion for child elements + // Check for