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:
@@ -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");
|
||||
|
||||
// UTF-8 ve HTML Entity fix
|
||||
$content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
// HTML Parse İşlemi
|
||||
// Hataları gizle (HTML5 etiketlerinde bazen uyarı verebilir)
|
||||
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);
|
||||
|
||||
$namePrefix = ucfirst($source);
|
||||
|
||||
HeaderTemplate::updateOrCreate(
|
||||
['title' => ucfirst($source) . ' Header'],
|
||||
[
|
||||
'html_content' => $html,
|
||||
'is_active' => true,
|
||||
'default_data' => []
|
||||
]
|
||||
);
|
||||
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);
|
||||
|
||||
$data = [];
|
||||
$counts = [];
|
||||
|
||||
FooterTemplate::updateOrCreate(
|
||||
['title' => ucfirst($source) . ' Footer'],
|
||||
[
|
||||
'html_content' => $html,
|
||||
'is_active' => true,
|
||||
'default_data' => []
|
||||
]
|
||||
);
|
||||
// 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);
|
||||
$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);
|
||||
|
||||
// 3. Placeholder Değişimi (Basit string replace)
|
||||
foreach ($this->replacements as $search => $replace) {
|
||||
$html = str_replace($search, $replace, $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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user