소스 검색

add layauts

sark 1 개월 전
부모
커밋
fcf0cffbc2

+ 26 - 0
bin/migrate.php

@@ -67,6 +67,7 @@ try {
         ['grupa' => 'content', 'name' => 'faq-title', 'value' => 'Frequently Asked Questions'],
         ['grupa' => 'content', 'name' => 'footer__text', 'value' => 'Your trusted casino partner since 2020. Licensed and regulated for your safety.'],
         ['grupa' => 'content', 'name' => 'copyright', 'value' => '© 2024 Amazing Casino. All rights reserved.'],
+        ['grupa' => 'content', 'name' => 'contact-menu-name', 'value' => 'Contacts'],
         ['grupa' => 'content', 'name' => 'review-count', 'value' => '1250'],
         ['grupa' => 'content', 'name' => 'listing', 'value' => 'Top Rated Casinos'],
         ['grupa' => 'content', 'name' => 'content_btn', 'value' => 'Play Now'],
@@ -103,6 +104,7 @@ try {
         ['grupa' => 'styles', 'name' => 'faq-title-font-color', 'value' => '#2c3e50'],
         ['grupa' => 'styles', 'name' => 'faq-title-hover-font-color', 'value' => '#e94560'],
         ['grupa' => 'styles', 'name' => 'burger-color', 'value' => '#ffffff'],
+        ['grupa' => 'styles', 'name' => 'burger-color2', 'value' => '#ffffff'],
 
         // SEO settings
         ['grupa' => 'seo', 'name' => 'title', 'value' => 'Amazing Casino - Best Online Gaming Experience 2024'],
@@ -134,6 +136,30 @@ try {
         }
     }
     
+    // Migration: Create seo_metas table
+    $seoMetasTableSQL = "
+    CREATE TABLE IF NOT EXISTS seo_metas (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        seoable_type VARCHAR(255) NOT NULL,
+        seoable_id INTEGER NOT NULL,
+        title VARCHAR(255) NULL,
+        description TEXT NULL,
+        keywords TEXT NULL,
+        canonical VARCHAR(255) NULL,
+        image VARCHAR(255) NULL,
+        extra_fields JSON NULL,
+        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+    )";
+    
+    $db->execute($seoMetasTableSQL);
+    echo "✓ SEO Metas table checked/created\n";
+    
+    // Create index for seo_metas table
+    $seoMetasIndexSQL = "CREATE INDEX IF NOT EXISTS idx_seo_metas_seoable ON seo_metas(seoable_type, seoable_id)";
+    $db->execute($seoMetasIndexSQL);
+    echo "✓ SEO Metas table index checked/created\n";
+    
     echo "Migration completed successfully!\n";
     
 } catch (Exception $e) {

BIN
database/database.db


+ 117 - 19
resources/view/admin/pages/form.php

@@ -96,26 +96,124 @@ ob_start();
                         </div>
                     </div>
                 <?php else: ?>
-                    <!-- For existing pages, show title field and layout-specific forms -->
-                    <div>
-                        <label for="title" class="block text-sm font-medium text-gray-700 mb-2">Page Title</label>
-                        <input type="text" name="title" id="title"
-                               value="<?= htmlspecialchars($page['title'] ?? '') ?>" 
-                               class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
-                    </div>
+                    <!-- Tabs System for Content and SEO -->
+                    <div x-data="{ activePageTab: 'content' }" class="mt-6">
+                        <!-- Tabs Navigation -->
+                        <div class="bg-white rounded-lg shadow-sm">
+                            <div class="border-b border-gray-200">
+                                <nav class="-mb-px flex space-x-8 px-6">
+                                    <button type="button" @click="activePageTab = 'content'" 
+                                            :class="activePageTab === 'content' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
+                                            class="py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors">
+                                        <svg class="w-5 h-5 inline mr-2" fill="currentColor" viewBox="0 0 20 20">
+                                            <path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/>
+                                            <path fill-rule="evenodd" d="M4 5a2 2 0 012-2h8a2 2 0 012 2v6a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 1a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/>
+                                        </svg>
+                                        Content
+                                    </button>
+                                    <button type="button" @click="activePageTab = 'seo'" 
+                                            :class="activePageTab === 'seo' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
+                                            class="py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors">
+                                        <svg class="w-5 h-5 inline mr-2" fill="currentColor" viewBox="0 0 20 20">
+                                            <path fill-rule="evenodd" d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd"/>
+                                        </svg>
+                                        SEO & Meta
+                                    </button>
+                                </nav>
+                            </div>
+                        </div>
 
-                    <!-- Layout-Specific Forms (only for edit mode) -->
-                    <div>
-                        <?php 
-                        $currentLayout = $page['layout'] ?? 'default';
-                        $layoutFormPath = __DIR__ . "/layouts/{$currentLayout}.php";
-                        if (file_exists($layoutFormPath)) {
-                            include $layoutFormPath;
-                        } else {
-                            // Fallback to default form
-                            include __DIR__ . "/layouts/default.php";
-                        }
-                        ?>
+                        <!-- Content Tab -->
+                        <div x-show="activePageTab === 'content'" 
+                             x-transition:enter="transition ease-out duration-200"
+                             x-transition:enter-start="opacity-0"
+                             x-transition:enter-end="opacity-100"
+                             x-transition:leave="transition ease-in duration-150"
+                             x-transition:leave-start="opacity-100"
+                             x-transition:leave-end="opacity-0"
+                             class="bg-white rounded-lg shadow-sm mt-6">
+                            <div class="p-6">
+                                <!-- Page Title -->
+                                <div class="mb-6">
+                                    <label for="title" class="block text-sm font-medium text-gray-700 mb-2">Page Title</label>
+                                    <input type="text" name="title" id="title"
+                                           value="<?= htmlspecialchars($page['title'] ?? '') ?>" 
+                                           class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
+                                </div>
+
+                                <!-- Layout-Specific Forms -->
+                                <?php 
+                                $currentLayout = $page['layout'] ?? 'default';
+                                $layoutFormPath = __DIR__ . "/layouts/{$currentLayout}.php";
+                                if (file_exists($layoutFormPath)) {
+                                    include $layoutFormPath;
+                                } else {
+                                    // Fallback to default form
+                                    include __DIR__ . "/layouts/default.php";
+                                }
+                                ?>
+                            </div>
+                        </div>
+
+                        <!-- SEO Tab -->
+                        <div x-show="activePageTab === 'seo'" 
+                             x-transition:enter="transition ease-out duration-200"
+                             x-transition:enter-start="opacity-0"
+                             x-transition:enter-end="opacity-100"
+                             x-transition:leave="transition ease-in duration-150"
+                             x-transition:leave-start="opacity-100"
+                             x-transition:leave-end="opacity-0"
+                             class="bg-white rounded-lg shadow-sm mt-6">
+                            <div class="p-6">
+                                <h3 class="text-lg font-medium text-gray-900 mb-6">SEO & Meta Information</h3>
+                                
+                                <div class="space-y-6">
+                                    <div>
+                                        <label for="seo_title" class="block text-sm font-medium text-gray-700 mb-2">Meta Title</label>
+                                        <input type="text" name="seo[title]" id="seo_title"
+                                               value="<?= htmlspecialchars($seoMeta['title'] ?? '') ?>" 
+                                               class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                                               placeholder="Enter SEO title for search engines">
+                                        <p class="text-xs text-gray-500 mt-1">Recommended length: 50-60 characters</p>
+                                    </div>
+
+                                    <div>
+                                        <label for="seo_description" class="block text-sm font-medium text-gray-700 mb-2">Meta Description</label>
+                                        <textarea name="seo[description]" id="seo_description" rows="3"
+                                                  class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                                                  placeholder="Enter meta description for search engines"><?= htmlspecialchars($seoMeta['description'] ?? '') ?></textarea>
+                                        <p class="text-xs text-gray-500 mt-1">Recommended length: 150-160 characters</p>
+                                    </div>
+
+                                    <div>
+                                        <label for="seo_keywords" class="block text-sm font-medium text-gray-700 mb-2">Meta Keywords</label>
+                                        <input type="text" name="seo[keywords]" id="seo_keywords"
+                                               value="<?= htmlspecialchars($seoMeta['keywords'] ?? '') ?>" 
+                                               class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                                               placeholder="keyword1, keyword2, keyword3">
+                                        <p class="text-xs text-gray-500 mt-1">Separate keywords with commas</p>
+                                    </div>
+
+                                    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
+                                        <div>
+                                            <label for="seo_canonical" class="block text-sm font-medium text-gray-700 mb-2">Canonical URL</label>
+                                            <input type="url" name="seo[canonical]" id="seo_canonical"
+                                                   value="<?= htmlspecialchars($seoMeta['canonical'] ?? '') ?>" 
+                                                   class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                                                   placeholder="https://example.com/page">
+                                        </div>
+
+                                        <div>
+                                            <label for="seo_image" class="block text-sm font-medium text-gray-700 mb-2">Social Share Image</label>
+                                            <input type="url" name="seo[image]" id="seo_image"
+                                                   value="<?= htmlspecialchars($seoMeta['image'] ?? '') ?>" 
+                                                   class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                                                   placeholder="https://example.com/image.jpg">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 <?php endif; ?>
 

+ 0 - 25
resources/view/admin/pages/layouts/default.php

@@ -6,29 +6,4 @@
                   class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                   placeholder="Enter your page content here..."><?= htmlspecialchars($page['content'] ?? '') ?></textarea>
     </div>
-
-    <!-- Basic Extra Fields for Default Layout -->
-    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
-        <div>
-            <label for="meta_description" class="block text-sm font-medium text-gray-700 mb-2">Meta Description</label>
-            <textarea name="extra_fields[meta_description]" id="meta_description" rows="3"
-                      class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
-                      placeholder="Brief description for search engines..."><?= htmlspecialchars($page['extra_fields']['meta_description'] ?? '') ?></textarea>
-        </div>
-
-        <div>
-            <label for="meta_keywords" class="block text-sm font-medium text-gray-700 mb-2">Meta Keywords</label>
-            <input type="text" name="extra_fields[meta_keywords]" id="meta_keywords"
-                   value="<?= htmlspecialchars($page['extra_fields']['meta_keywords'] ?? '') ?>"
-                   class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
-                   placeholder="keyword1, keyword2, keyword3">
-        </div>
-    </div>
-
-    <div>
-        <label for="custom_css" class="block text-sm font-medium text-gray-700 mb-2">Custom CSS (Optional)</label>
-        <textarea name="extra_fields[custom_css]" id="custom_css" rows="4"
-                  class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
-                  placeholder=".custom-style { color: #333; }"><?= htmlspecialchars($page['extra_fields']['custom_css'] ?? '') ?></textarea>
-    </div>
 </div>

+ 0 - 21
resources/view/admin/pages/layouts/front_page.php

@@ -68,25 +68,4 @@
                   class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                   placeholder="Enter main content for the homepage..."><?= htmlspecialchars($page['content'] ?? '') ?></textarea>
     </div>
-
-    <!-- SEO Settings for Homepage -->
-    <div class="bg-gray-50 rounded-lg p-4">
-        <h4 class="text-md font-medium text-gray-800 mb-4">SEO Settings</h4>
-        <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
-            <div>
-                <label for="meta_description" class="block text-sm font-medium text-gray-700 mb-2">Meta Description</label>
-                <textarea name="extra_fields[meta_description]" id="meta_description" rows="3"
-                          class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
-                          placeholder="Homepage description for search engines..."><?= htmlspecialchars($page['extra_fields']['meta_description'] ?? '') ?></textarea>
-            </div>
-
-            <div>
-                <label for="og_image" class="block text-sm font-medium text-gray-700 mb-2">Social Share Image URL</label>
-                <input type="text" name="extra_fields[og_image]" id="og_image"
-                       value="<?= htmlspecialchars($page['extra_fields']['og_image'] ?? '') ?>"
-                       class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
-                       placeholder="/uploads/homepage-share.jpg">
-            </div>
-        </div>
-    </div>
 </div>

+ 115 - 447
resources/view/home.php

@@ -1,458 +1,126 @@
-<!DOCTYPE html>
-<html lang="<?= $isHomepage ? ($seo['lang'] ?? 'en') : ($seo['lang-page'] ?? 'en-US') ?>">
-
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <base href="/">
-    <title><?= $content['title'] ?></title>
-    <link rel="stylesheet" href="css/styles.css">
-    <meta name='robots' content='index, follow' />
-    <?php if (!empty($content['favicon'])): ?>
-        <link rel="icon" href="<?= htmlspecialchars($content['favicon']) ?>" type="image/x-icon">
-    <?php endif; ?>
-    <meta property="og:type" content="<?= $seo['og-type'] ?? '' ?>">
-    <meta property="og:title" content="<?= $seo['og-title'] ?? '' ?>">
-    <meta property="og:description" content="<?= $seo['og-description'] ?? '' ?>">
-    <meta property="og:locale" content="<?= $seo['og-locale'] ?? '' ?>">
-    <meta property="og:image" content="<?= htmlspecialchars($seo['og-image'] ?? '') ?>">
-    <meta name="description" content="<?= $seo['description'] ?? '' ?>">
-
-    <link rel="canonical" href="<?= $seo['canonical'] ?? '' ?>">
-
-    <script type="application/ld+json">
-<?= json_encode([
-    "@context" => "https://schema.org",
-    "@graph" => [
-        [
-            "@type" => "WebPage",
-            "name" => $content['title'],
-            "description" => $content['description'],
-            "inLanguage" => $content['lang'],
-            "url" => $currentUrl,
-            "dateModified" => $content['modified-date'] ?? '' , // ISO формат
-            "author" => [
-                "@type" => "Person",
-                "name" => $content['author-name'] ?? '',
-                "image" => $currentDomain . '/' . htmlspecialchars($content['author-img'] ?? '')
-            ]
-        ],
-        [
-            "@type" => "Organization",
-            "name" => $domainName,
-            "url" => $currentDomain,
-            "logo" => $currentDomain . '/' . htmlspecialchars($content['uploaded_image'] ?? '')
-        ],
-        [
-            "@type" => "BreadcrumbList",
-            "itemListElement" => [
-                [
-                    "@type" => "ListItem",
-                    "position" => 1,
-                    "name" => $content['title-h1'],
-                    "item" => $currentUrl
-                ]
-            ]
-        ],
-        !empty($casinoItems) ? [
-            "@type" => "ItemList",
-            "name" => htmlspecialchars($content['listing']),
-            "itemListElement" => array_map(function ($item, $index) use ($currentDomain, $currentReviewCount) {
-                $data = json_decode($item['value'], true);
-                return [
-                    "@type" => "ListItem",
-                    "position" => $index + 1,
-                    "item" => [
-                        "@type" => "Offer",
-                        "name" => htmlspecialchars($data['heading']),
-                        "description" => htmlspecialchars($data['text']),
-                        "url" => $currentDomain . '/#' . getSlug($data['heading']),
-                        "offeredBy" => [
-                            "@type" => "Organization",
-                            "name" => htmlspecialchars($data['heading']),
-                            "logo" => $currentDomain . '/' . htmlspecialchars($data['image']),
-                            "aggregateRating" => [
-                                "@type" => "AggregateRating",
-                                "ratingValue" => "5",
-                                "reviewCount" => $currentReviewCount + ($index + 1),
-                                "bestRating" => "5",
-                                "worstRating" => "1"
-                            ]
-                        ]
-                    ]
-                ];
-            }, $casinoItems, array_keys($casinoItems))
-        ] : null,
-        [
-            "@type" => "FAQPage",
-            "mainEntity" => array_map(function ($item) {
-                return [
-                    "@type" => "Question",
-                    "name" => htmlspecialchars($item['question']),
-                    "acceptedAnswer" => [
-                        "@type" => "Answer",
-                        "text" => htmlspecialchars($item['answer'])
-                    ]
-                ];
-            }, $faqItems)
-        ]
-    ]
-], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); ?>
-    </script>
-
-
-    <style>
-        th{
-            color: <?= $styles['pass_btn_color'] ?? '' ?>;
-            background-color: <?= $styles['accent_color'] ?? '' ?>;
-        }
-        .burger:before,
-        .burger:after,
-        .burger {
-            background-color: <?= $styles['burger-color'] ?? '' ?>;
-        }
-
-        .main-font {
-            font-family: <?= $styles['main-fz'] ?? '' ?>;
-        }
-
-        .main-color {
-            background: <?= $styles['main_color'] ?? '' ?>;
-        }
-
-        .header-color {
-            background: <?= $styles['header_color'] ?? '' ?>;
-        }
-
-        .login-btn-bg {
-            background: <?= $styles['login_btn_bg'] ?? '' ?>;
-        }
-
-        .accent-color {
-            background: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        button.accent-color {
-            transition: filter 0.3s ease;
-        }
-
-        button.accent-color:hover {
-            filter: brightness(0.85);
-        }
-
-        .faq-title-font-color {
-            color: <?= $styles['faq-title-font-color'] ?? '' ?>;
-        }
-
-        .faq-title-hover-font-color:hover {
-            color: <?= $styles['faq-title-hover-font-color'] ?? '' ?>;
-        }
-
-        .font-color {
-            color: <?= $styles['font_color'] ?? '' ?>;
-        }
-
-        a {
-            color: <?= $styles['link_color'] ?? '' ?>;
-            text-decoration: none;
-            transition: 300ms;
-        }
-
-        a:hover {
-            color: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        .secondary-color {
-            background-color: <?= $styles['secondary_color'] ?? '' ?>;
-        }
-
-        .main-fz {
-            color: <?= $styles['main_fz'] ?? '' ?>;
-        }
-
-        .footer-color {
-            background: <?= $styles['footer_color'] ?? '' ?>;
-        }
-
-        .accordion-header.active {
-            background: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        .accordion-header:hover {
-            background: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        .header__menu-list {
-            background: <?= $styles['header_color'] ?? '' ?>;
-        }
-
-        .login-btn-color {
-            color: <?= $styles['login_btn_color'] ?? '' ?>;
-        }
-
-        .pass-btn-color {
-            color: <?= $styles['pass_btn_color'] ?? '' ?>;
-        }
-
-        table {
-            background: <?= $styles['table'] ?? '' ?>;
-        }
-
-        caption {
-            background: <?= $styles['caption'] ?? '' ?>;
-        }
-
-        .casinos__bonus {
-            color: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        h2 {
-            color: inherit;
-        }
-
-        h2::after {
-            background: <?= $styles['accent_color'] ?? '' ?>;
-        }
-
-        @keyframes pulse {
-            0% {
-                transform: scale(0.95);
-                box-shadow: 0 0 0 0;
-            }
-
-            70% {
-                transform: scale(1);
-                box-shadow: 0 4px 24px 0 <?= $styles['accent_color'] ?? '' ?>;
-            }
-
-            100% {
-                transform: scale(0.95);
-                box-shadow: 0 0 0 0 transparent;
-            }
-        }
-
-        ul {
-            list-style: none;
-        }
-
-        .main__text ul li {
-            position: relative;
-            margin: 5px 0;
-        }
-
-        .main__text ul li::before {
-            content: '✓';
-            width: 22px;
-            height: 22px;
-            border-radius: 50%;
-            background: <?= $styles['accent_color'] ?? '' ?>;
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            margin-right: 10px;
-            font-size: 15px;
-        }
-
-        .secondary__text ul li {
-            position: relative;
-            margin: 5px 0;
-        }
-
-        .secondary__text ul li::before {
-            content: '✓';
-            width: 23px;
-            height: 23px;
-            border-radius: 50%;
-            background: <?= $styles['accent_color'] ?? '' ?>;
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            margin-right: 10px;
-            font-size: 15px;
-        }
-
-        .bottom-btn {
-            margin: auto;
-            width: 200px;
-            background: #c30054;
-        }
-
-        ol {
-            list-style: none;
-            counter-reset: list-counter;
-            padding-left: 0;
-        }
-
-        ol li {
-            counter-increment: list-counter;
-            position: relative;
-            padding-left: 40px;
-            /* Отступ для цифры */
-            margin: 15px 0;
-        }
-
-        ol li::before {
-            content: counter(list-counter);
-            position: absolute;
-            left: 0;
-            width: 25px;
-            height: 25px;
-            border: 1px solid <?= $styles['accent_color'] ?? '' ?>;
-            border-radius: 50%;
-            text-align: center;
-            line-height: 25px;
-            font-weight: bold;
-            top: -1px;
-            font-size: 14px;
-        }
-
-        .main__btn {
-            position: relative;
-            margin: auto;
-
-        }
-
-        .main__btn a {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            height: 100%;
-            width: 100%;
-            color: <?= $styles['pass_btn_color'] ?? '' ?>;
-        }
-
-        .main__btn a:hover {
-            color: <?= $styles['pass_btn_color'] ?? '' ?>;
-        }
-    </style>
-</head>
-
-<body class="main-color font-color main-font">
-    <?php include __DIR__ . '/partials/header.php'; ?>
-    <main class="main">
-        <section>
-            <div class="container">
-                <h1 class="main__title"><?= $content['title'] ?></h1>
-                <div class="main__banner">
-                    <?php if (!empty($bannerLogo['src'])): ?>
-                        <img class="redirect-js" 
-                             width="<?= $bannerLogo['width'] ?? '' ?>" 
-                             height="<?= $bannerLogo['height'] ?? '' ?>" 
-                             src="<?= htmlspecialchars($bannerLogo['src']) ?>" 
-                             alt="<?= $bannerLogo['alt'] ?? '' ?>" 
-                             title="<?= $bannerLogo['title'] ?? '' ?>">
-                    <?php endif; ?>
+<?php
+ob_start();
+?>
+<section>
+    <div class="container">
+        <h1 class="main__title"><?= $content['title'] ?></h1>
+        <div class="main__banner">
+            <?php if (!empty($bannerLogo['src'])): ?>
+                <img class="redirect-js"
+                    width="<?= $bannerLogo['width'] ?? '' ?>"
+                    height="<?= $bannerLogo['height'] ?? '' ?>"
+                    src="<?= htmlspecialchars($bannerLogo['src']) ?>"
+                    alt="<?= $bannerLogo['alt'] ?? '' ?>"
+                    title="<?= $bannerLogo['title'] ?? '' ?>">
+            <?php endif; ?>
+        </div>
+        <div class="container">
+            <?php if (!empty($content['author-img'])): ?>
+                <div class="author">
+                    <div class="author__img">
+                        <img width="" height="" src="<?= htmlspecialchars($content['author-img']) ?>" alt="<?= htmlspecialchars($content['author-name'] ?? '') ?>" title="<?= htmlspecialchars($content['author-name'] ?? '') ?>">
+                    </div>
+                    <div class="author__info">
+                        <div class="author__name"><?= htmlspecialchars($content['author-name'] ?? '') ?></div>
+                        <div class="author__date"> <?= $content['modified-date'] ?? '' ?></div>
+                    </div>
                 </div>
-                <div class="container">
-                    <?php if (!empty($content['author-img'])): ?>
-                        <div class="author">
-                            <div class="author__img">
-                                <img width="" height="" src="<?= htmlspecialchars($content['author-img']) ?>" alt="<?= htmlspecialchars($content['author-name'] ?? '') ?>" title="<?= htmlspecialchars($content['author-name'] ?? '') ?>">
-                            </div>
-                            <div class="author__info">
-                                <div class="author__name"><?= htmlspecialchars($content['author-name'] ?? '') ?></div>
-                                <div class="author__date"> <?= $content['modified-date'] ?? '' ?></div>
+            <?php endif; ?>
+            <div class="main__text">
+                <?= $content['top_text'] ?>
+            </div>
+            <?php if (empty($content['hide-btns'])): ?>
+                <div class="pulse__btn main__btn accent-color pass-btn-color redirect-js"><?= $content['content_btn'] ?></div>
+            <?php endif; ?>
+        </div>
+</section>
+<section class="casinos">
+
+    <div class="container casinos__wrap">
+        <?php if (!empty($casinoItems)): ?>
+            <h2 class="casinos__listing"><?= htmlspecialchars($content['listing']) ?></h2>
+        <?php endif; ?>
+
+        <?php if (!empty($casinoItems) && is_array($casinoItems)): ?>
+            <?php foreach ($casinoItems as $index => $item):
+                $isTopThree = $index < 3;
+            ?>
+                <div class="casinos__item <?= $isTopThree ? 'top-casino' : '' ?>" id="<?= getSlug(htmlspecialchars($item['heading'] ?? '')) ?>">
+                    <div class="casinos__info">
+                        <div class="casinos__number pass-btn-color accent-color"><?= $index + 1 ?></div>
+                        <div class="casinos__image">
+                            <div class="redirect-js" data-idp="<?= $index + 1 ?>" data-url="<?= htmlspecialchars($item['keitaro'] ?? '') ?>" data-label="repeater-button-<?= $index + 1 ?>">
+                                <?php if (!empty($item['image'])): ?>
+                                    <img
+                                        width="140" height="92"
+                                        class="lazy"
+                                        data-src="<?= htmlspecialchars($item['image']) ?>"
+                                        alt="<?= isset($item['alt']) ? htmlspecialchars($item['alt']) : '' ?>"
+                                        title="<?= isset($item['title']) ? htmlspecialchars($item['title']) : '' ?>">
+                                <?php endif; ?>
                             </div>
                         </div>
-                    <?php endif; ?>
-                    <div class="main__text">
-                        <?= $content['top_text'] ?>
-                    </div>
-                    <?php if (empty($content['hide-btns'])): ?>
-                        <div class="pulse__btn main__btn accent-color pass-btn-color redirect-js"><?= $content['content_btn'] ?></div>
-                     <?php endif; ?>
-                </div>
-        </section>
-        <section class="casinos">
-
-            <div class="container casinos__wrap">
-                <?php if (!empty($casinoItems)): ?>
-                    <h2 class="casinos__listing"><?= htmlspecialchars($content['listing']) ?></h2>
-                <?php endif; ?>
-
-                <?php if (!empty($casinoItems) && is_array($casinoItems)): ?>
-                    <?php foreach ($casinoItems as $index => $item):
-                        $isTopThree = ($index < 3);
-                    ?>
-                        <div class="casinos__item <?= $isTopThree ? 'top-casino' : '' ?>" id="<?= getSlug(htmlspecialchars($item['heading'] ?? '')) ?>">
-                            <div class="casinos__info">
-                                <div class="casinos__number pass-btn-color accent-color"><?= $index + 1 ?></div>
-                                <div class="casinos__image">
-                                    <div class="redirect-js" data-idp="<?= $index + 1 ?>" data-url="<?= htmlspecialchars($item['keitaro'] ?? '') ?>" data-label="repeater-button-<?= $index + 1 ?>">
-                                        <?php if (!empty($item['image'])): ?>
-                                            <img
-                                                width="140" height="92"
-                                                class="lazy"
-                                                data-src="<?= htmlspecialchars($item['image']) ?>"
-                                                alt="<?= isset($item['alt']) ? htmlspecialchars($item['alt']) : '' ?>"
-                                                title="<?= isset($item['title']) ? htmlspecialchars($item['title']) : '' ?>">
-                                        <?php endif; ?>
-                                    </div>
-                                </div>
-                                <div class="casinos__title">
-                                    <?php if (!empty($item['heading'])): ?>
-                                        <div><?= htmlspecialchars($item['heading']) ?></div>
-                                    <?php endif; ?>
-                                    <div class="casinos__rait">
-                                        <img data-src="images/Star.svg" class="lazy" alt="star" title="star" width="24" height="24">
-                                        <div class="casinos__rating-box">
-                                            <span class="casinos__rating-value"><?= $item['rating'] ?? calculateRating($index) ?> &nbsp;</span> / 10
-                                        </div>
-                                    </div>
+                        <div class="casinos__title">
+                            <?php if (!empty($item['heading'])): ?>
+                                <div><?= htmlspecialchars($item['heading']) ?></div>
+                            <?php endif; ?>
+                            <div class="casinos__rait">
+                                <img data-src="images/Star.svg" class="lazy" alt="star" title="star" width="24" height="24">
+                                <div class="casinos__rating-box">
+                                    <span class="casinos__rating-value"><?= $item['rating'] ?? calculateRating($index) ?> &nbsp;</span> / 10
                                 </div>
                             </div>
+                        </div>
+                    </div>
 
-                            <div class="casinos__bonus">
-                                <?= isset($item['text']) ? htmlspecialchars($item['text']) : '' ?>
-                            </div>
+                    <div class="casinos__bonus">
+                        <?= isset($item['text']) ? htmlspecialchars($item['text']) : '' ?>
+                    </div>
 
-                            <div class="casinos__button">
-                                <?php if (!empty($item['keitaro']) && !empty($item['button'])): ?>
-                                    <div class="redirect-js" data-idp="<?= $index + 1 ?>" data-url="<?= htmlspecialchars($item['keitaro']) ?>" data-label="repeater-button-<?= $index + 1 ?>">
-                                        <button class="accent-color pass-btn-color">
-                                            <?= htmlspecialchars($item['button']) ?>
-                                        </button>
-                                    </div>
-                                <?php endif; ?>
+                    <div class="casinos__button">
+                        <?php if (!empty($item['keitaro']) && !empty($item['button'])): ?>
+                            <div class="redirect-js" data-idp="<?= $index + 1 ?>" data-url="<?= htmlspecialchars($item['keitaro']) ?>" data-label="repeater-button-<?= $index + 1 ?>">
+                                <button class="accent-color pass-btn-color">
+                                    <?= htmlspecialchars($item['button']) ?>
+                                </button>
                             </div>
-                        </div>
-                    <?php endforeach; ?>
-                <?php endif; ?>
-            </div>
-
-        </section>
-        <section>
-            <div class="container">
-                <div class="secondary__text">
-                    <?= htmlspecialchars_decode($content['bottom_text']); ?>
+                        <?php endif; ?>
+                    </div>
                 </div>
-            </div>
-        </section>
-        <section>
-            <div class="container">
-                <h2><?= $content['faq-title'] ?? '' ?></h2>
-                <div class="accordion-container">
-                    <?php foreach ($faqItems as $item): ?>
-                        <div class="accordion-item">
-                            <h3 class="accordion-header faq-title-hover-font-color secondary-color faq-title-font-color">
-                                <?= htmlspecialchars($item['question']) ?>
-                                <span class="icon  faq-title-hover-font-color faq-title-font-color">+</span>
-                            </h3>
-                            <div class="accordion-content secondary-color faq-title-font-color">
-                                <p>
-                                    <?= htmlspecialchars($item['answer']) ?>
-                                </p>
-                            </div>
-                        </div>
-                    <?php endforeach; ?>
+            <?php endforeach; ?>
+        <?php endif; ?>
+    </div>
+
+</section>
+<section>
+    <div class="container">
+        <div class="secondary__text">
+            <?= htmlspecialchars_decode($content['bottom_text']); ?>
+        </div>
+    </div>
+</section>
+<section>
+    <div class="container">
+        <h2><?= $content['faq-title'] ?? '' ?></h2>
+        <div class="accordion-container">
+            <?php foreach ($faqItems as $item): ?>
+                <div class="accordion-item">
+                    <h3 class="accordion-header faq-title-hover-font-color secondary-color faq-title-font-color">
+                        <?= htmlspecialchars($item['question']) ?>
+                        <span class="icon  faq-title-hover-font-color faq-title-font-color">+</span>
+                    </h3>
+                    <div class="accordion-content secondary-color faq-title-font-color">
+                        <p>
+                            <?= htmlspecialchars($item['answer']) ?>
+                        </p>
+                    </div>
                 </div>
-            </div>
-        </section>
-
+            <?php endforeach; ?>
         </div>
-    </main>
-    <?php include __DIR__ . '/partials/footer.php'; ?>
-    <script src="js/script.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/blazy/1.8.2/blazy.min.js"></script>
-</body>
-</html>
+    </div>
+</section>
+
+<?php
+$pageContent = ob_get_clean();
+include __DIR__ . '/layouts/main.php';
+?>

+ 112 - 0
resources/view/layouts/main.php

@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="<?= $isHomepage ? ($seo['lang'] ?? 'en') : ($seo['lang-page'] ?? 'en-US') ?>">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <base href="/">
+    <title><?= $content['title'] ?></title>
+    <link rel="stylesheet" href="css/styles.css">
+    <meta name='robots' content='index, follow' />
+    <?php if (!empty($content['favicon'])): ?>
+        <link rel="icon" href="<?= htmlspecialchars($content['favicon']) ?>" type="image/x-icon">
+    <?php endif; ?>
+    <meta property="og:type" content="<?= $seo['og-type'] ?? '' ?>">
+    <meta property="og:title" content="<?= $seo['og-title'] ?? '' ?>">
+    <meta property="og:description" content="<?= $seo['og-description'] ?? '' ?>">
+    <meta property="og:locale" content="<?= $seo['og-locale'] ?? '' ?>">
+    <meta property="og:image" content="<?= htmlspecialchars($seo['og-image'] ?? '') ?>">
+    <meta name="description" content="<?= $seo['description'] ?? '' ?>">
+
+    <link rel="canonical" href="<?= $seo['canonical'] ?? '' ?>">
+
+    <script type="application/ld+json">
+<?= json_encode([
+    "@context" => "https://schema.org",
+    "@graph" => [
+        [
+            "@type" => "WebPage",
+            "name" => $content['title'],
+            "description" => $content['description'],
+            "inLanguage" => $content['lang'],
+            "url" => $currentUrl,
+            "dateModified" => $content['modified-date'] ?? '' , // ISO формат
+            "author" => [
+                "@type" => "Person",
+                "name" => $content['author-name'] ?? '',
+                "image" => $currentDomain . '/' . htmlspecialchars($content['author-img'] ?? '')
+            ]
+        ],
+        [
+            "@type" => "Organization",
+            "name" => $domainName,
+            "url" => $currentDomain,
+            "logo" => $currentDomain . '/' . htmlspecialchars($content['uploaded_image'] ?? '')
+        ],
+        [
+            "@type" => "BreadcrumbList",
+            "itemListElement" => [
+                [
+                    "@type" => "ListItem",
+                    "position" => 1,
+                    "name" => $content['title-h1'],
+                    "item" => $currentUrl
+                ]
+            ]
+        ],
+        !empty($casinoItems) ? [
+            "@type" => "ItemList",
+            "name" => htmlspecialchars($content['listing']),
+            "itemListElement" => array_map(function ($item, $index) use ($currentDomain, $currentReviewCount) {
+                return [
+                    "@type" => "ListItem",
+                    "position" => $index + 1,
+                    "item" => [
+                        "@type" => "Offer",
+                        "name" => htmlspecialchars($item['heading']),
+                        "description" => htmlspecialchars($item['text']),
+                        "url" => $currentDomain . '/#' . getSlug($item['heading']),
+                        "offeredBy" => [
+                            "@type" => "Organization",
+                            "name" => htmlspecialchars($item['heading']),
+                            "logo" => $currentDomain . '/' . htmlspecialchars($item['image']),
+                            "aggregateRating" => [
+                                "@type" => "AggregateRating",
+                                "ratingValue" => "5",
+                                "reviewCount" => $currentReviewCount + ($index + 1),
+                                "bestRating" => "5",
+                                "worstRating" => "1"
+                            ]
+                        ]
+                    ]
+                ];
+            }, $casinoItems, array_keys($casinoItems))
+        ] : null,
+        [
+            "@type" => "FAQPage",
+            "mainEntity" => array_map(function ($item) {
+                return [
+                    "@type" => "Question",
+                    "name" => htmlspecialchars($item['question']),
+                    "acceptedAnswer" => [
+                        "@type" => "Answer",
+                        "text" => htmlspecialchars($item['answer'])
+                    ]
+                ];
+            }, $faqItems)
+        ]
+    ]
+], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); ?>
+    </script>
+    <?php include __DIR__ . '/partials/styles.php'; ?>
+</head>
+
+<body class="main-color font-color main-font">
+    <?php include __DIR__ . '/partials/header.php'; ?>
+    <main class="main">
+        <?= $pageContent ?>
+    </main>
+    <?php include __DIR__ . '/partials/footer.php'; ?>
+    <script src="js/script.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/blazy/1.8.2/blazy.min.js"></script>
+</body>
+</html>

+ 6 - 1
resources/view/partials/footer.php → resources/view/layouts/partials/footer.php

@@ -1,3 +1,8 @@
+<?php
+$content = (new \App\Settings('content'))->getAll();
+$footerLogo = json_decode($content['footer-logo'] ?? '{}', true);
+$currentDomain = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
+?>
 <footer class="footer footer-color" style="color:#ffffff;">
         <div class="container">
             <div class="footer__content">
@@ -19,7 +24,7 @@
                 </div> -->
             </div>
             <ul class="footer__menu">
-                <li><a href="<?=$currentDomain.'/contacts' ?>"><?= $content['contact-menu-name'] ?? '' ?></a></li>
+                <li><a href="<?= $currentDomain . '/contacts' ?>"><?= $content['contact-menu-name'] ?? '' ?></a></li>
             </ul>
             <ul class="footer__bottom-imgs">
                 <li><span><img data-src="images/18+.png" class="lazy" alt="18+" title="18+" width="40" height="40"></span></li>

+ 5 - 0
resources/view/partials/header.php → resources/view/layouts/partials/header.php

@@ -1,3 +1,8 @@
+<?php
+$content = (new \App\Settings('content'))->getAll();
+$headerLogo = json_decode($content['header-logo'] ?? '{}', true);
+$menuItems = json_decode($content['menu'] ?? '[]', true);
+?>
 <header class="header header-color">
         <div class="container">
             <div class="header__logo">

+ 227 - 0
resources/view/layouts/partials/styles.php

@@ -0,0 +1,227 @@
+<?php
+$styles = (new \App\Settings('styles'))->getAll();
+?>
+<style>
+    th{
+        color: <?= $styles['pass_btn_color'] ?? '' ?>;
+        background-color: <?= $styles['accent_color'] ?? '' ?>;
+    }
+    .burger:before,
+    .burger:after,
+    .burger {
+        background-color: <?= $styles['burger-color'] ?? '' ?>;
+    }
+
+    .main-font {
+        font-family: <?= $styles['main-fz'] ?? '' ?>;
+    }
+
+    .main-color {
+        background: <?= $styles['main_color'] ?? '' ?>;
+    }
+
+    .header-color {
+        background: <?= $styles['header_color'] ?? '' ?>;
+    }
+
+    .login-btn-bg {
+        background: <?= $styles['login_btn_bg'] ?? '' ?>;
+    }
+
+    .accent-color {
+        background: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    button.accent-color {
+        transition: filter 0.3s ease;
+    }
+
+    button.accent-color:hover {
+        filter: brightness(0.85);
+    }
+
+    .faq-title-font-color {
+        color: <?= $styles['faq-title-font-color'] ?? '' ?>;
+    }
+
+    .faq-title-hover-font-color:hover {
+        color: <?= $styles['faq-title-hover-font-color'] ?? '' ?>;
+    }
+
+    .font-color {
+        color: <?= $styles['font_color'] ?? '' ?>;
+    }
+
+    a {
+        color: <?= $styles['link_color'] ?? '' ?>;
+        text-decoration: none;
+        transition: 300ms;
+    }
+
+    a:hover {
+        color: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    .secondary-color {
+        background-color: <?= $styles['secondary_color'] ?? '' ?>;
+    }
+
+    .main-fz {
+        color: <?= $styles['main_fz'] ?? '' ?>;
+    }
+
+    .footer-color {
+        background: <?= $styles['footer_color'] ?? '' ?>;
+    }
+
+    .accordion-header.active {
+        background: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    .accordion-header:hover {
+        background: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    .header__menu-list {
+        background: <?= $styles['header_color'] ?? '' ?>;
+    }
+
+    .login-btn-color {
+        color: <?= $styles['login_btn_color'] ?? '' ?>;
+    }
+
+    .pass-btn-color {
+        color: <?= $styles['pass_btn_color'] ?? '' ?>;
+    }
+
+    table {
+        background: <?= $styles['table'] ?? '' ?>;
+    }
+
+    caption {
+        background: <?= $styles['caption'] ?? '' ?>;
+    }
+
+    .casinos__bonus {
+        color: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    h2 {
+        color: inherit;
+    }
+
+    h2::after {
+        background: <?= $styles['accent_color'] ?? '' ?>;
+    }
+
+    @keyframes pulse {
+        0% {
+            transform: scale(0.95);
+            box-shadow: 0 0 0 0;
+        }
+
+        70% {
+            transform: scale(1);
+            box-shadow: 0 4px 24px 0 <?= $styles['accent_color'] ?? '' ?>;
+        }
+
+        100% {
+            transform: scale(0.95);
+            box-shadow: 0 0 0 0 transparent;
+        }
+    }
+
+    ul {
+        list-style: none;
+    }
+
+    .main__text ul li {
+        position: relative;
+        margin: 5px 0;
+    }
+
+    .main__text ul li::before {
+        content: '✓';
+        width: 22px;
+        height: 22px;
+        border-radius: 50%;
+        background: <?= $styles['accent_color'] ?? '' ?>;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 10px;
+        font-size: 15px;
+    }
+
+    .secondary__text ul li {
+        position: relative;
+        margin: 5px 0;
+    }
+
+    .secondary__text ul li::before {
+        content: '✓';
+        width: 23px;
+        height: 23px;
+        border-radius: 50%;
+        background: <?= $styles['accent_color'] ?? '' ?>;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 10px;
+        font-size: 15px;
+    }
+
+    .bottom-btn {
+        margin: auto;
+        width: 200px;
+        background: #c30054;
+    }
+
+    ol {
+        list-style: none;
+        counter-reset: list-counter;
+        padding-left: 0;
+    }
+
+    ol li {
+        counter-increment: list-counter;
+        position: relative;
+        padding-left: 40px;
+        /* Отступ для цифры */
+        margin: 15px 0;
+    }
+
+    ol li::before {
+        content: counter(list-counter);
+        position: absolute;
+        left: 0;
+        width: 25px;
+        height: 25px;
+        border: 1px solid <?= $styles['accent_color'] ?? '' ?>;
+        border-radius: 50%;
+        text-align: center;
+        line-height: 25px;
+        font-weight: bold;
+        top: -1px;
+        font-size: 14px;
+    }
+
+    .main__btn {
+        position: relative;
+        margin: auto;
+
+    }
+
+    .main__btn a {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 100%;
+        width: 100%;
+        color: <?= $styles['pass_btn_color'] ?? '' ?>;
+    }
+
+    .main__btn a:hover {
+        color: <?= $styles['pass_btn_color'] ?? '' ?>;
+    }
+</style>

+ 11 - 0
src/Models/Page.php

@@ -67,6 +67,17 @@ class Page
         return $this->db->execute("DELETE FROM pages WHERE id = ?", [$id]);
     }
     
+    public function updateOrCreate($slug, $data)
+    {
+        $existing = $this->getBySlug($slug);
+        
+        if ($existing) {
+            return $this->update($existing['id'], $data);
+        } else {
+            return $this->create($data);
+        }
+    }
+    
     public function exists($slug, $excludeId = null)
     {
         if ($excludeId) {

+ 137 - 0
src/Models/SeoMeta.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace App\Models;
+
+use App\Database;
+
+class SeoMeta
+{
+    private $db;
+    
+    public function __construct()
+    {
+        $this->db = Database::getInstance();
+    }
+    
+    public function getAll()
+    {
+        return $this->db->fetchAll("SELECT * FROM seo_metas ORDER BY created_at DESC");
+    }
+    
+    public function getById($id)
+    {
+        return $this->db->fetchOne("SELECT * FROM seo_metas WHERE id = ?", [$id]);
+    }
+    
+    public function getByMorphable($seoableType, $seoableId)
+    {
+        return $this->db->fetchOne(
+            "SELECT * FROM seo_metas WHERE seoable_type = ? AND seoable_id = ?", 
+            [$seoableType, $seoableId]
+        );
+    }
+    
+    public function create($data)
+    {
+        $sql = "INSERT INTO seo_metas (seoable_type, seoable_id, title, description, keywords, canonical, image, extra_fields) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+        $params = [
+            $data['seoable_type'],
+            $data['seoable_id'],
+            $data['title'] ?? null,
+            $data['description'] ?? null,
+            $data['keywords'] ?? null,
+            $data['canonical'] ?? null,
+            $data['image'] ?? null,
+            $data['extra_fields'] ? json_encode($data['extra_fields']) : null
+        ];
+        
+        $this->db->execute($sql, $params);
+        return $this->db->lastInsertId();
+    }
+    
+    public function update($id, $data)
+    {
+        $sql = "UPDATE seo_metas SET seoable_type = ?, seoable_id = ?, title = ?, description = ?, keywords = ?, canonical = ?, image = ?, extra_fields = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?";
+        $params = [
+            $data['seoable_type'],
+            $data['seoable_id'],
+            $data['title'] ?? null,
+            $data['description'] ?? null,
+            $data['keywords'] ?? null,
+            $data['canonical'] ?? null,
+            $data['image'] ?? null,
+            $data['extra_fields'] ? json_encode($data['extra_fields']) : null,
+            $id
+        ];
+        
+        return $this->db->execute($sql, $params);
+    }
+    
+    public function updateOrCreate($seoableType, $seoableId, $data)
+    {
+        $existing = $this->getByMorphable($seoableType, $seoableId);
+        
+        $data['seoable_type'] = $seoableType;
+        $data['seoable_id'] = $seoableId;
+        
+        if ($existing) {
+            return $this->update($existing['id'], $data);
+        } else {
+            return $this->create($data);
+        }
+    }
+    
+    public function delete($id)
+    {
+        return $this->db->execute("DELETE FROM seo_metas WHERE id = ?", [$id]);
+    }
+    
+    public function deleteByMorphable($seoableType, $seoableId)
+    {
+        return $this->db->execute(
+            "DELETE FROM seo_metas WHERE seoable_type = ? AND seoable_id = ?", 
+            [$seoableType, $seoableId]
+        );
+    }
+    
+    public function exists($seoableType, $seoableId)
+    {
+        $result = $this->db->fetchOne(
+            "SELECT COUNT(*) as count FROM seo_metas WHERE seoable_type = ? AND seoable_id = ?", 
+            [$seoableType, $seoableId]
+        );
+        return $result['count'] > 0;
+    }
+    
+    // Helper methods for any entity type
+    public function getForRecord($entityType, $entityId)
+    {
+        return $this->getByMorphable($entityType, $entityId);
+    }
+    
+    public function createForRecord($entityType, $entityId, $data)
+    {
+        return $this->updateOrCreate($entityType, $entityId, $data);
+    }
+    
+    public function deleteForRecord($entityType, $entityId)
+    {
+        return $this->deleteByMorphable($entityType, $entityId);
+    }
+    
+    // Convenience methods for specific entity types
+    public function getForPage($pageId)
+    {
+        return $this->getForRecord('Page', $pageId);
+    }
+    
+    public function createForPage($pageId, $data)
+    {
+        return $this->createForRecord('Page', $pageId, $data);
+    }
+    
+    public function deleteForPage($pageId)
+    {
+        return $this->deleteForRecord('Page', $pageId);
+    }
+}

+ 24 - 46
src/Pages/admin/pages.php

@@ -1,9 +1,11 @@
 <?php
 
 use App\Models\Page;
+use App\Models\SeoMeta;
 use App\Enums\PageLayout;
 
 $pageModel = new Page();
+$seoMetaModel = new SeoMeta();
 $action = $_GET['action'] ?? 'list';
 $id = $_GET['id'] ?? null;
 
@@ -11,11 +13,6 @@ $id = $_GET['id'] ?? null;
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
     $data = $_POST;
     
-    // Debug: check what data we received
-    error_log('POST data received: ' . print_r($_POST, true));
-    error_log('Current action: ' . $action);
-    error_log('Form action: ' . ($data['action'] ?? 'not set'));
-    
     // Generate slug from name if not provided
     if (empty($data['slug']) && !empty($data['name'])) {
         $data['slug'] = getSlug($data['name']);
@@ -26,21 +23,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
         $data['layout'] = PageLayout::Default->value;
     }
     
-    // Handle extra_fields - can be array or JSON string
-    if (isset($data['extra_fields'])) {
-        if (is_array($data['extra_fields'])) {
-            // Form data comes as array, keep as array for JSON encoding
-            $data['extra_fields'] = $data['extra_fields'];
-        } elseif (is_string($data['extra_fields']) && !empty($data['extra_fields'])) {
-            // Manual JSON input, decode and re-encode to validate
-            $decoded = json_decode($data['extra_fields'], true);
-            $data['extra_fields'] = is_array($decoded) ? $decoded : [];
-        } else {
-            $data['extra_fields'] = [];
-        }
-    } else {
-        $data['extra_fields'] = [];
-    }
+    // Handle extra_fields
+    $data['extra_fields'] = $data['extra_fields'] ?? [];
     
     // Get action from form data
     $formAction = $data['action'] ?? $action;
@@ -48,40 +32,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
     try {
         switch ($formAction) {
             case 'create':
-                // Check if slug already exists
-                if ($pageModel->exists($data['slug'])) {
-                    $error = "Page with this slug already exists!";
-                } else {
-                    $newPageId = $pageModel->create($data);
-                    if ($newPageId) {
-                        $success = "Page created successfully! Redirecting to edit...";
-                        error_log("Page created with ID: $newPageId");
-                        // Redirect to edit the newly created page
-                        header("Location: /admin/pages/?action=edit&id=" . $newPageId);
-                        exit;
-                    } else {
-                        $error = "Failed to create page. Please check the database connection.";
-                        error_log("Failed to create page, no ID returned");
-                    }
+            case 'edit':
+                $pageId = $pageModel->updateOrCreate($data['slug'], $data);
+                
+                // Save SEO data if provided
+                if (!empty($data['seo'])) {
+                    $seoMetaModel->updateOrCreate('Page', $pageId, $data['seo']);
                 }
-                break;
                 
-            case 'edit':
-                if ($id) {
-                    // Check if slug exists (excluding current page)
-                    if ($pageModel->exists($data['slug'], $id)) {
-                        $error = "Another page with this slug already exists!";
-                    } else {
-                        $pageModel->update($id, $data);
-                        $success = "Page updated successfully!";
-                        $action = 'list'; // Redirect to list
-                    }
+                if ($formAction === 'create') {
+                    $success = "Page created successfully! Redirecting to edit...";
+                    // Redirect to edit the newly created page
+                    header("Location: /admin/pages/?action=edit&id=" . $pageId);
+                    exit;
+                } else {
+                    $success = "Page updated successfully!";
+                    $action = 'list'; // Redirect to list
                 }
                 break;
                 
             case 'delete':
                 if ($id) {
                     $pageModel->delete($id);
+                    // Delete associated SEO data
+                    $seoMetaModel->deleteForRecord('page', $id);
                     $success = "Page deleted successfully!";
                     $action = 'list'; // Redirect to list
                 }
@@ -121,6 +95,9 @@ switch ($action) {
                 } else {
                     $page['extra_fields'] = [];
                 }
+                
+                // Load SEO data
+                $seoMeta = $seoMetaModel->getForRecord('Page', $id) ?: [];
             }
         } else {
             $action = 'list';
@@ -148,6 +125,7 @@ return ViewRender($template, [
     'action' => $action,
     'pages' => $pages ?? [],
     'page' => $page ?? null,
+    'seoMeta' => $seoMeta ?? [],
     'layouts' => $layouts,
     'success' => $success ?? null,
     'error' => $error ?? null

+ 30 - 67
src/Pages/home.php

@@ -1,68 +1,32 @@
 <?php
 
-// Обновление счетчика просмотров
-$contentSettings = settings('content');
-$currentReviewCount = $contentSettings->get('review-count') ?: 0;
-$currentReviewCount++;
-$contentSettings->set('review-count', $currentReviewCount);
-// Получение данных из настроек
-$content = settings('content')->getAll();
-$styles = settings('styles')->getAll();
-$seo = settings('seo')->getAll();
-// Получение repeater данных из content группы
-$faqItems = [];
-if (isset($content['faq']) && !empty($content['faq'])) {
-    $decoded = json_decode($content['faq'], true);
-    $faqItems = is_array($decoded) ? $decoded : [];
-}
+use App\Settings;
 
-$menuItems = [];
-if (isset($content['menu']) && !empty($content['menu'])) {
-    $decoded = json_decode($content['menu'], true);
-    $menuItems = is_array($decoded) ? $decoded : [];
-}
+// Получение данных из настроек одним запросом
+$content = (new Settings('content'))->getAll();
+$seo = (new Settings('seo'))->getAll();
 
-$casinoItems = [];
-if (isset($content['casino_list']) && !empty($content['casino_list'])) {
-    $decoded = json_decode($content['casino_list'], true);
-    $casinoItems = is_array($decoded) ? $decoded : [];
-}
+// Parse JSON fields needed for view
+$bannerLogo = json_decode($content['banner-logo'] ?? '{}', true);
+$casinoItems = json_decode($content['casino_list'] ?? '[]', true);
+$faqItems = json_decode($content['faq'] ?? '[]', true);
 
-// Получение JSON данных для лого
-$headerLogo = [];
-if (isset($content['header-logo']) && !empty($content['header-logo'])) {
-    $decoded = json_decode($content['header-logo'], true);
-    $headerLogo = is_array($decoded) ? $decoded : [];
-}
-
-$bannerLogo = [];
-if (isset($content['banner-logo']) && !empty($content['banner-logo'])) {
-    $decoded = json_decode($content['banner-logo'], true);
-    $bannerLogo = is_array($decoded) ? $decoded : [];
-}
-
-$footerLogo = [];
-if (isset($content['footer-logo']) && !empty($content['footer-logo'])) {
-    $decoded = json_decode($content['footer-logo'], true);
-    $footerLogo = is_array($decoded) ? $decoded : [];
-}
-
-// Определение протокола и домена
+// Domain and URL info for JSON-LD
 $currentDomain = getCurrentDomain();
 $currentUrl = getCurrentUrl();
 
 // Формирование URL для hash полей
-$hash_1 = htmlspecialchars($content['hash-1'] ?? '');
-$hash_2 = htmlspecialchars($content['hash-2'] ?? '');
-$hash_3 = htmlspecialchars($content['hash-3'] ?? '');
-$hash_4 = htmlspecialchars($content['hash-4'] ?? '');
-$hash_5 = htmlspecialchars($content['hash-5'] ?? '');
+// $hash_1 = htmlspecialchars($content['hash-1'] ?? '');
+// $hash_2 = htmlspecialchars($content['hash-2'] ?? '');
+// $hash_3 = htmlspecialchars($content['hash-3'] ?? '');
+// $hash_4 = htmlspecialchars($content['hash-4'] ?? '');
+// $hash_5 = htmlspecialchars($content['hash-5'] ?? '');
 
-$url1 = "http://66.55.159.98/{$hash_1}";
-$url2 = "http://66.55.159.98/{$hash_2}";
-$url3 = "http://66.55.159.98/{$hash_3}";
-$url4 = "http://66.55.159.98/{$hash_4}";
-$url5 = "http://66.55.159.98/{$hash_5}";
+// $url1 = "http://66.55.159.98/{$hash_1}";
+// $url2 = "http://66.55.159.98/{$hash_2}";
+// $url3 = "http://66.55.159.98/{$hash_3}";
+// $url4 = "http://66.55.159.98/{$hash_4}";
+// $url5 = "http://66.55.159.98/{$hash_5}";
 
 // Casino данные теперь получаются через $casinoItems выше
 
@@ -83,25 +47,24 @@ $isSearchBot = $isGoogleBot || $isBingBot;
 
 return ViewRender('home.php', [
     'content' => $content,
-    'styles' => $styles,
     'seo' => $seo,
     'faqItems' => $faqItems,
-    'menuItems' => $menuItems,
-    'casinoItems' => $casinoItems,
-    'headerLogo' => $headerLogo,
-    'bannerLogo' => $bannerLogo,
-    'footerLogo' => $footerLogo,
-    'currentReviewCount' => $currentReviewCount,
+    //'menuItems' => $menuItems,
+    // 'casinoItems' => $casinoItems,
+    // 'headerLogo' => $headerLogo,
+    // 'bannerLogo' => $bannerLogo,
+    // 'footerLogo' => $footerLogo,
+    //'currentReviewCount' => $currentReviewCount,
     'currentDomain' => $currentDomain,
     'currentUrl' => $currentUrl,
     'isHomepage' => $is_homepage,
     'isSearchBot' => $isSearchBot,
     'userIp' => $ip,
     'urls' => [
-        'url1' => $url1,
-        'url2' => $url2,
-        'url3' => $url3,
-        'url4' => $url4,
-        'url5' => $url5
+        // 'url1' => $url1,
+        // 'url2' => $url2,
+        // 'url3' => $url3,
+        // 'url4' => $url4,
+        // 'url5' => $url5
     ]
 ]);