diff --git a/app/Console/Commands/ImportHtmlTemplates.php b/app/Console/Commands/ImportHtmlTemplates.php index 59e2c44..9aeb840 100644 --- a/app/Console/Commands/ImportHtmlTemplates.php +++ b/app/Console/Commands/ImportHtmlTemplates.php @@ -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; } } - diff --git a/database/seeders/SettingSeeder.php b/database/seeders/SettingSeeder.php index 6f15bea..d440d32 100644 --- a/database/seeders/SettingSeeder.php +++ b/database/seeders/SettingSeeder.php @@ -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']); + } } } }