Refactor ImportHtmlTemplates Command: Enhanced the command to process HTML templates by introducing a more structured approach for extracting header, footer, and section templates. Implemented dynamic placeholder replacements and improved asset path handling. Added support for light and dark logo settings in the database seeder, ensuring better logo management for different themes.

This commit is contained in:
Ümit Tunç
2025-12-27 07:57:48 +03:00
parent bacd5d7309
commit 8244bcc74c
2 changed files with 247 additions and 101 deletions
+207 -97
View File
@@ -9,32 +9,28 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use DOMDocument;
use DOMXPath;
use DOMElement;
class ImportHtmlTemplates extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'theme:import-html {--force : Mevcut şablonları günceller}';
protected $description = 'HTML şablonlarını analiz eder, gelişmiş placeholderları oluşturur ve veritabanına aktarır.';
/**
* The console command description.
*
* @var string
*/
protected $description = 'public/html klasöründeki HTML şablonlarını analiz eder ve veritabanına aktarır.';
/**
* Otomatik değiştirilecek metinler ve placeholder karşılıkları
*/
protected $replacements = [
'Truncgil Technology' => '{text.company_name}',
'Trunçgil' => '{text.company_name}',
'tel:+' => 'tel:{setting.phone}',
'mailto:' => 'mailto:{setting.email}',
'© 2025' => '© {date.year}',
// Etiket -> Kategori/Prefix eşleşmesi
protected $tagMappings = [
'h1' => 'text.title',
'h2' => 'text.title',
'h3' => 'text.subtitle',
'h4' => 'text.subtitle',
'h5' => 'text.heading',
'h6' => 'text.heading',
'p' => 'text.description',
'span' => 'text.content',
'address' => 'text.address',
'button' => 'text.button',
'a' => 'url.link',
'li' => 'text.list_item',
'label' => 'text.label',
];
public function handle()
@@ -51,7 +47,6 @@ class ImportHtmlTemplates extends Command
foreach ($files as $file) {
if ($file->getExtension() !== 'html') continue;
$this->processFile($file);
}
@@ -62,121 +57,237 @@ class ImportHtmlTemplates extends Command
{
$filename = $file->getFilenameWithoutExtension();
$content = File::get($file->getPathname());
$this->line("İşleniyor: $filename");
// HTML Parse İşlemi
// Hataları gizle (HTML5 etiketlerinde bazen uyarı verebilir)
// UTF-8 ve HTML Entity fix
$content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8');
libxml_use_internal_errors(true);
$dom = new DOMDocument();
// UTF-8 karakter sorunu yaşamamak için hack
// mb_convert_encoding ile içeriğin UTF-8 olduğundan emin oluyoruz
$content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8');
// HTML5 tagleri için ayarlar
$dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
// 1. HEADER ÇIKARTMA
// 1. HEADER
$headerNodes = $xpath->query('//header');
if ($headerNodes->length > 0) {
$headerHtml = $this->getInnerHtml($headerNodes->item(0));
$this->createHeaderTemplate($filename, $headerHtml);
$this->createTemplate($filename, 'header', $headerNodes->item(0));
}
// 2. FOOTER ÇIKARTMA
// 2. FOOTER
$footerNodes = $xpath->query('//footer');
if ($footerNodes->length > 0) {
$footerHtml = $this->getInnerHtml($footerNodes->item(0));
$this->createFooterTemplate($filename, $footerHtml);
$this->createTemplate($filename, 'footer', $footerNodes->item(0));
}
// 3. SECTIONS (BODY İÇİNDEKİLER)
// Header ve Footer haricindeki sectionları bul
// 3. SECTIONS
$sections = $xpath->query('//section');
foreach ($sections as $index => $section) {
$sectionHtml = $this->getInnerHtml($section);
$sectionId = $section->getAttribute('id') ?: 'section-' . ($index + 1);
if (strlen($section->textContent) < 10 && $section->getElementsByTagName('img')->length == 0) continue;
// ID varsa al yoksa oluştur
$sectionId = $section->getAttribute('id');
if (empty($sectionId)) {
$sectionId = 'section-' . ($index + 1);
}
// Eğer section çok kısaysa atla (boş sectionlar için)
if (strlen(strip_tags($sectionHtml)) < 10) continue;
$this->createSectionTemplate($filename, $sectionId, $sectionHtml);
$this->createTemplate($filename, 'section', $section, $sectionId);
}
}
protected function createHeaderTemplate($source, $html)
protected function createTemplate($source, $type, DOMElement $node, $sectionId = null)
{
$html = $this->processHtmlContent($html);
$result = $this->processNodeAndExtractData($node, $type);
HeaderTemplate::updateOrCreate(
['title' => ucfirst($source) . ' Header'],
[
'html_content' => $html,
'is_active' => true,
'default_data' => []
]
);
$namePrefix = ucfirst($source);
if ($type === 'header') {
HeaderTemplate::updateOrCreate(
['title' => "$namePrefix Header"],
[
'html_content' => $result['html'],
'default_data' => $result['data'],
'is_active' => true
]
);
$this->line("✓ Header eklendi: $source");
}
elseif ($type === 'footer') {
FooterTemplate::updateOrCreate(
['title' => "$namePrefix Footer"],
[
'html_content' => $result['html'],
'default_data' => $result['data'],
'is_active' => true
]
);
$this->line("✓ Footer eklendi: $source");
}
elseif ($type === 'section') {
$title = "$namePrefix - " . ucfirst(str_replace(['-', '_'], ' ', $sectionId));
SectionTemplate::updateOrCreate(
['title' => $title],
[
'html_content' => $result['html'],
'default_data' => $result['data'],
'is_active' => true
]
);
$this->line("✓ Section eklendi: $title");
}
}
protected function createFooterTemplate($source, $html)
protected function processNodeAndExtractData(DOMElement $originalNode, $templateType)
{
$html = $this->processHtmlContent($html);
$node = $originalNode->cloneNode(true);
$xpath = new DOMXPath($node->ownerDocument);
FooterTemplate::updateOrCreate(
['title' => ucfirst($source) . ' Footer'],
[
'html_content' => $html,
'is_active' => true,
'default_data' => []
]
);
$data = [];
$counts = [];
// A. Header Özel Alanları
if ($templateType === 'header') {
// Logo
$brands = $xpath->query('.//*[contains(@class, "navbar-brand")]//img', $node);
foreach ($brands as $img) {
$src = $this->fixAssetPath($img->getAttribute('src'));
$class = $img->getAttribute('class');
if (str_contains($class, 'logo-dark')) {
$data['setting.logo_dark'] = $src;
$img->setAttribute('src', '{setting.logo_dark}');
} elseif (str_contains($class, 'logo-light')) {
$data['setting.logo_light'] = $src;
$img->setAttribute('src', '{setting.logo_light}');
} else {
$data['setting.logo'] = $src;
$img->setAttribute('src', '{setting.logo}');
}
}
// Menu
$navs = $xpath->query('.//*[contains(@class, "navbar-nav")]', $node);
foreach ($navs as $nav) {
if (!$nav->parentNode) continue;
// İçini temizle
while ($nav->hasChildNodes()) {
$nav->removeChild($nav->firstChild);
}
// Placeholder metni ekle - Token olarak
$nav->nodeValue = '___SPECIAL_MENU___';
}
// Dil Seçici
$langs = $xpath->query('.//*[contains(@class, "language")]', $node);
foreach ($langs as $lang) {
$lang->nodeValue = '___SETTING_LANGUAGES___';
}
}
// B. Genel İşlemler
// 1. Resimler
$images = $xpath->query('.//img', $node);
foreach ($images as $img) {
$src = $img->getAttribute('src');
if (str_starts_with($src, '{')) continue;
if (empty($src)) continue;
$fixedSrc = $this->fixAssetPath($src);
$key = $this->generateKey('image.content', $counts);
$data[$key] = $fixedSrc;
$img->setAttribute('src', "{" . $key . "}");
$alt = $img->getAttribute('alt');
if (!empty($alt) && !str_starts_with($alt, '{')) {
$altKey = str_replace('image.content', 'text.alt', $key);
$data[$altKey] = $alt;
$img->setAttribute('alt', "{" . $altKey . "}");
}
}
// 2. Linkler
$links = $xpath->query('.//a', $node);
foreach ($links as $link) {
$href = $link->getAttribute('href');
if (empty($href) || str_starts_with($href, '#') || str_starts_with($href, 'javascript:') || str_starts_with($href, 'mailto:') || str_starts_with($href, 'tel:') || str_starts_with($href, '{')) continue;
$fixedHref = $this->fixAssetPath($href);
if (str_ends_with($fixedHref, '.html')) {
$fixedHref = str_replace('.html', '', $fixedHref);
if($fixedHref == 'index') $fixedHref = '/';
}
$key = $this->generateKey('url.link', $counts);
$data[$key] = $fixedHref;
$link->setAttribute('href', "{" . $key . "}");
}
// 3. Metinler
$textNodes = $xpath->query('.//text()[normalize-space()]', $node);
foreach ($textNodes as $textNode) {
$parent = $textNode->parentNode;
if (in_array(strtolower($parent->nodeName), ['script', 'style', 'noscript', 'iframe'])) continue;
$textContent = trim($textNode->textContent);
// Özel tokenlarımızı atla
if (str_contains($textContent, '___SPECIAL_') || str_contains($textContent, '___SETTING_')) continue;
if (str_starts_with($textContent, '{') && str_ends_with($textContent, '}')) continue;
if (mb_strlen($textContent) < 2) continue;
$tagName = strtolower($parent->nodeName);
$prefix = $this->tagMappings[$tagName] ?? 'text.content';
$key = $this->generateKey($prefix, $counts);
$data[$key] = $textContent;
$textNode->nodeValue = "{" . $key . "}";
}
$html = $this->getInnerHtml($node);
$html = $this->fixAssetPathInString($html);
// ENCODE FIX: %7B -> {, %7D -> }
// DOMDocument attribute değerlerini encode ettiği için bunları geri çeviriyoruz
$html = str_replace(['%7B', '%7D'], ['{', '}'], $html);
// SPECIAL TOKENS FIX
if ($templateType === 'header') {
$html = str_replace('___SPECIAL_MENU___', '{special.menu}', $html);
$html = str_replace('___SETTING_LANGUAGES___', '{setting.available_languages}', $html);
}
return ['html' => $html, 'data' => $data];
}
protected function createSectionTemplate($source, $sectionId, $html)
protected function generateKey($prefix, &$counts)
{
$html = $this->processHtmlContent($html);
$title = ucfirst($source) . ' - ' . ucfirst(str_replace(['-', '_'], ' ', $sectionId));
SectionTemplate::updateOrCreate(
['title' => $title],
[
'html_content' => $html,
'is_active' => true,
'default_data' => []
]
);
if (!isset($counts[$prefix])) {
$counts[$prefix] = 1;
} else {
$counts[$prefix]++;
}
return $prefix . '_' . $counts[$prefix];
}
/**
* HTML İçeriğini temizler, asset yollarını düzeltir ve placeholderları yerleştirir
*/
protected function processHtmlContent($html)
protected function fixAssetPath($path)
{
if (str_starts_with($path, 'assets/')) {
return '/html/' . $path;
}
return $path;
}
protected function fixAssetPathInString($html)
{
// 1. Asset Yollarını Düzelt (assets/img -> /html/assets/img)
// Public/html altında olduğu için absolute path veriyoruz
$html = str_replace('src="assets/', 'src="/html/assets/', $html);
$html = str_replace('href="assets/', 'href="/html/assets/', $html);
$html = str_replace("src='assets/", "src='/html/assets/", $html);
// 2. Linkleri Düzelt (index.html -> /)
$html = str_replace('href="index.html"', 'href="/"', $html);
// 3. Placeholder Değişimi (Basit string replace)
foreach ($this->replacements as $search => $replace) {
$html = str_replace($search, $replace, $html);
}
$html = str_replace("href='assets/", "href='/html/assets/", $html);
$html = str_replace('url(assets/', 'url(/html/assets/', $html);
$html = str_replace('url("assets/', 'url("/html/assets/', $html);
$html = str_replace("url('assets/", "url('/html/assets/", $html);
return $html;
}
/**
* DOMNode'un HTML içeriğini string olarak alır
*/
protected function getInnerHtml($node)
{
$innerHTML = '';
@@ -187,4 +298,3 @@ class ImportHtmlTemplates extends Command
return $innerHTML;
}
}
+40 -4
View File
@@ -585,6 +585,26 @@ class SettingSeeder extends Seeder
'is_public' => true,
'is_active' => true,
],
[
'key' => 'logo_light',
'value' => 'assets/img/logo-light.png',
'type' => 'file',
'group' => 'layout',
'label' => 'Light Logo',
'description' => 'Koyu zeminler için açık renkli logo',
'is_public' => true,
'is_active' => true,
],
[
'key' => 'logo_dark',
'value' => 'assets/img/logo-dark.png',
'type' => 'file',
'group' => 'layout',
'label' => 'Dark Logo',
'description' => 'Açık zeminler için koyu renkli logo',
'is_public' => true,
'is_active' => true,
],
[
'key' => 'favicon_path',
'value' => 'assets/img/favicon.ico',
@@ -1298,10 +1318,26 @@ class SettingSeeder extends Seeder
];
foreach ($settings as $setting) {
Setting::updateOrCreate(
['key' => $setting['key']],
$setting
);
$existing = Setting::where('key', $setting['key'])->first();
if ($existing) {
$updated = false;
// Sadece değişmişse güncelle
foreach ($setting as $field => $value) {
if ($existing->$field != $value) {
$updated = true;
break;
}
}
if ($updated) {
$existing->update($setting);
$this->command->info("Setting updated: " . $setting['key']);
} else {
$this->command->info("Setting unchanged: " . $setting['key']);
}
} else {
Setting::create($setting);
$this->command->info("Setting created: " . $setting['key']);
}
}
}
}