Skip to content

Commit

Permalink
Tiding up, basics of adding media and moved from storage to a temp di…
Browse files Browse the repository at this point in the history
…rectory package for importing files.
  • Loading branch information
jamesdordoy committed Oct 15, 2024
1 parent 7f45c32 commit 9c59308
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Check failure on line 16 in database/migrations/2024_10_05_create_htmlable_elements_table.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to an undefined method Illuminate\Database\Schema\ForeignIdColumnDefinition::onDelete().
$table->foreignIdFor(Element::class, 'parent_id')->nullable()->onDelete('cascade');

Check failure on line 17 in database/migrations/2024_10_05_create_htmlable_elements_table.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to an undefined method Illuminate\Database\Schema\ForeignIdColumnDefinition::onDelete().
$table->string('tag');
$table->timestamps();
Expand Down
85 changes: 27 additions & 58 deletions src/Actions/Documents/Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,95 +3,64 @@
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
{
public function __invoke(ZipArchive $zip, Model $model, bool $validate = true): Document
{
$document = app(Create::class)($model, pathinfo($zip->filename, PATHINFO_FILENAME), '<!DOCTYPE html>');

// 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 [];
}
}
}
8 changes: 8 additions & 0 deletions src/Actions/Elements/UpdateInline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace JamesDordoy\HTMLable\Actions\Elements;

class UpdateInline
{
public function __invoke() {}
}
4 changes: 4 additions & 0 deletions src/Actions/RegisterRoutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use JamesDordoy\HTMLable\Http\Controllers\Documents\ParseDocumentController;
use JamesDordoy\HTMLable\Http\Controllers\Elements\ElementsController;
use JamesDordoy\HTMLable\Http\Controllers\Elements\ParseElementController;
use JamesDordoy\HTMLable\Http\Controllers\Media\ServeMediaController;
use JamesDordoy\HTMLable\Http\Controllers\Media\ServeMediaSignedController;
use JamesDordoy\HTMLable\Http\Controllers\ValuesController;

class RegisterRoutes
Expand All @@ -19,5 +21,7 @@ public function __invoke()
Route::resource('/htmlable/elements', ElementsController::class);
Route::get('/htmlable/elements/{element}/render', ParseElementController::class);
Route::resource('/htmlable/values', ValuesController::class);
Route::get('/htmlable/media/{media}/serve-signed', ServeMediaSignedController::class)->middleware(['signed'])->name('htmlable.media.serve-signed');
Route::get('/htmlable/media/{media}/serve', ServeMediaController::class);
}
}
16 changes: 16 additions & 0 deletions src/Http/Controllers/Media/ServeMediaController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace JamesDordoy\HTMLable\Http\Controllers\Media;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class ServeMediaController extends Controller
{
public function __invoke(Request $request, Media $media)
{
return $media->toInlineResponse($request);
}
}

16 changes: 16 additions & 0 deletions src/Http/Controllers/Media/ServeMediaSignedController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace JamesDordoy\HTMLable\Http\Controllers\Media;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class ServeMediaSignedController extends Controller
{
public function __invoke(Request $request, Media $media)
{
return $media->toInlineResponse($request);
}
}

84 changes: 45 additions & 39 deletions src/Models/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -35,27 +37,7 @@ public function render(): HtmlString
$rootElement = $this->elements()->whereNull('parent_id')->first();
$docType = $this->doctype ?? '<!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}</{$tag}>\n";
return new HtmlString("{$docType}{$rootElement->render()}");
}

/**
Expand Down Expand Up @@ -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 <style> tags specifically
if ($node->nodeName === 'style') {
// Save the content of the <style> tag
Value::create([
'element_id' => $element->id,
'key' => 'content',
'value' => trim($node->nodeValue),
]);
} else {
// Process child nodes
foreach ($node->childNodes as $childNode) {
// If the child node is a text node, save its content
if ($childNode->nodeType === XML_TEXT_NODE) {
if (!empty(trim($childNode->nodeValue))) {
Value::create([
'element_id' => $element->id,
'key' => 'content',
'value' => trim($childNode->nodeValue),
]);
}
} elseif ($childNode->nodeType === XML_ELEMENT_NODE) {
// Recursively process child elements
$this->saveElement($childNode, $element->id);
}
}
}
}

protected function storeAssetAndGetSignedPath(string $source): string
protected function getSignedPath(string $source): string
{
return $source;
$media = Media::where('name', pathinfo($source, PATHINFO_FILENAME))->first();

return URL::signedRoute('htmlable.media.serve-signed', ['media' => $media->id]);
}
}
7 changes: 7 additions & 0 deletions src/Traits/UsesHTML.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
namespace JamesDordoy\HTMLable\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use JamesDordoy\HTMLable\Models\Document;

trait UsesHTML
{
public function getHtmlableModel(): Model
{
return $this;
}

public function documents(): HasMany
{
return $this->hasMany(Document::class);
}
}

0 comments on commit 9c59308

Please sign in to comment.