sark 1 сар өмнө
parent
commit
a1c915b327

+ 1 - 0
public/index.php

@@ -12,6 +12,7 @@ $page = match (true) {
   ('/editor/uploads/' === $requestUri) => 'upload.php',
   ('/verstka/' === $requestUri) => 'verstka.php',
   (preg_match('#^/redirect/([\w-]+)/$#', $requestUri, $matches) === 1) => 'redirect.php',
+  (preg_match('#^/([\w-]+)/$#', $requestUri, $matches) === 1) => 'page.php',
   default => '404.php',
 };
 

+ 1 - 77
resources/view/layouts/main.php

@@ -22,83 +22,7 @@ $settingsContent = settings('content')->getAll();
     <link rel="canonical" href="<?= $seo['canonical'] ?? '' ?>">
 
     <script type="application/ld+json">
-<?= json_encode([
-    "@context" => "https://schema.org",
-    "@graph" => [
-        [
-            "@type" => "WebPage",
-            "name" => $settingsContent['title'],
-            "description" => $settingsContent['description'],
-            "inLanguage" => $settingsContent['lang'],
-            "url" => $currentUrl,
-            "dateModified" => $settingsContent['modified-date'] ?? '' , // ISO формат
-            "author" => [
-                "@type" => "Person",
-                "name" => $settingsContent['author-name'] ?? '',
-                "image" => $currentDomain . '/' . htmlspecialchars($settingsContent['author-img'] ?? '')
-            ]
-        ],
-        [
-            "@type" => "Organization",
-            "name" => $domainName,
-            "url" => $currentDomain,
-            "logo" => $currentDomain . '/' . htmlspecialchars($settingsContent['uploaded_image'] ?? '')
-        ],
-        [
-            "@type" => "BreadcrumbList",
-            "itemListElement" => [
-                [
-                    "@type" => "ListItem",
-                    "position" => 1,
-                    "name" => $settingsContent['title-h1'],
-                    "item" => $currentUrl
-                ]
-            ]
-        ],
-        !empty($casinoItems) ? [
-            "@type" => "ItemList",
-            "name" => htmlspecialchars($settingsContent['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); ?>
+        <?php include __DIR__ . '/../partials/schema.php'; ?>
     </script>
     <?php include __DIR__ . '/partials/styles.php'; ?>
 </head>

+ 71 - 0
resources/view/pages/default.php

@@ -0,0 +1,71 @@
+<?php
+$settingsContent = settings('content')->getAll();
+?>
+<!DOCTYPE html>
+<html lang="<?= $seo['extra_fields']['locale'] ?? 'en' ?>">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <base href="/">
+    <title><?= $seo['title'] ?></title>
+    <link rel="stylesheet" href="css/styles.css">
+    <meta name='robots' content='index, follow' />
+    <?php if (!empty($settingsContent['favicon'])): ?>
+        <link rel="icon" href="<?= htmlspecialchars($settingsContent['favicon']) ?>" type="image/x-icon">
+    <?php endif; ?>
+    <meta property="og:title" content="<?= $seo['title'] ?? '' ?>">
+    <meta property="og:description" content="<?= $seo['description'] ?? '' ?>">
+    <meta property="og:locale" content="<?= $seo['extra_fields']['locale'] ?? '' ?>">
+    <meta property="og:image" content="<?= htmlspecialchars($seo['image'] ?? '') ?>">
+    <meta name="description" content="<?= $seo['description'] ?? '' ?>">
+
+    <link rel="canonical" href="<?= $seo['canonical'] ?? '' ?>">
+
+    <script type="application/ld+json">
+        <?php include __DIR__ . '/../partials/schema.php'; ?>
+    </script>
+    <?php include __DIR__ . '/../partials/styles.php'; ?>
+</head>
+
+<body>
+    <?php include __DIR__ . '/../partials/header.php'; ?>
+    <main class="main">
+        <?php if (!empty($topContent)): ?>
+            <section class="top-content">
+                <div class="container">
+                    <?= $topContent ?>
+                </div>
+            </section>
+        <?php endif; ?>
+        
+        <section class="page-content">
+            <div class="container">
+                <h1><?= htmlspecialchars($pageData['name']) ?></h1>
+                <div class="content">
+                    <?= $pageContent ?>
+                </div>
+            </div>
+        </section>
+
+        <?php if (!empty($faqItems)): ?>
+            <section class="faq-section">
+                <div class="container">
+                    <h2>FAQ</h2>
+                    <div class="faq-list">
+                        <?php foreach ($faqItems as $faq): ?>
+                            <div class="faq-item">
+                                <h3><?= htmlspecialchars($faq['question']) ?></h3>
+                                <div class="faq-answer">
+                                    <?= $faq['answer'] ?>
+                                </div>
+                            </div>
+                        <?php endforeach; ?>
+                    </div>
+                </section>
+        <?php endif; ?>
+    </main>
+    <?php include __DIR__ . '/../partials/footer.php'; ?>
+    <script src="js/script.js"></script>
+</body>
+
+</html>

+ 197 - 0
resources/view/partials/schema.php

@@ -0,0 +1,197 @@
+<?php
+$schemaGraph = [];
+
+// 1. Organization
+$schemaGraph[] = [
+    "@type" => "Organization",
+    "@id" => $currentUrl . "#organization",
+    "name" => $seo['title'] ?? $pageContent['title'],
+    "url" => $currentDomain,
+    "logo" => !empty($settingsContent['header-logo']) ? $currentDomain . '/' . json_decode($settingsContent['header-logo'], true)['src'] : '',
+    "sameAs" => !empty($settingsContent['official-sites']) ? json_decode($settingsContent['official-sites'], true) : []
+];
+
+// 2. Casino Brand
+$casinoBrand = [
+    "@type" => ["Casino", "Brand"],
+    "@id" => $currentUrl . "#brand-casino-entity",
+    "name" => $seo['title'],
+    "url" => $seo['canonical'] ?? $currentUrl,
+    "image" => !empty($settingsContent['header-logo']) ? $currentDomain . '/' . json_decode($settingsContent['header-logo'], true)['src'] : '',
+    "aggregateRating" => [
+        "@type" => "AggregateRating",
+        "ratingValue" => "5",
+        "ratingCount" => $settingsContent['rating-count'] ?? "150",
+        "worstRating" => "1",
+        "bestRating" => "5"
+    ],
+    "priceRange" => "$",
+    "openingHoursSpecification" => [
+        [
+            "@type" => "OpeningHoursSpecification",
+            "dayOfWeek" => ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+            "opens" => "00:00",
+            "closes" => "23:59"
+        ]
+    ],
+    "parentOrganization" => [
+        "@id" => $currentUrl . "#organization"
+    ]
+];
+
+// Add address if any field is filled
+if (!empty($settingsContent['address-street']) || !empty($settingsContent['address-city']) || 
+    !empty($settingsContent['address-region']) || !empty($settingsContent['address-postal']) || 
+    !empty($settingsContent['address-country'])) {
+    $casinoBrand["address"] = [
+        "@type" => "PostalAddress",
+        "streetAddress" => $settingsContent['address-street'] ?? '',
+        "addressLocality" => $settingsContent['address-city'] ?? '',
+        "addressRegion" => $settingsContent['address-region'] ?? '',
+        "postalCode" => $settingsContent['address-postal'] ?? '',
+        "addressCountry" => $settingsContent['address-country'] ?? ''
+    ];
+}
+
+$schemaGraph[] = $casinoBrand;
+
+// 3. WebSite
+$schemaGraph[] = [
+    "@type" => "WebSite",
+    "url" => $currentDomain,
+    "name" => $settingsContent['title-h1'],
+    "publisher" => [
+        "@id" => $currentUrl . "#organization"
+    ]
+];
+
+// 4. WebPage
+$schemaGraph[] = [
+    "@type" => "WebPage",
+    "@id" => $currentUrl . "#webpage",
+    "name" => $seo['title'],
+    "description" => $seo['description'],
+    "inLanguage" => $seo['extra_fields']['locale'] ?? 'en',
+    "url" => $currentUrl,
+    "dateModified" => $settingsContent['modified-date'] ?? date('Y-m-d'),
+    "author" => [
+        "@type" => "Person",
+        "name" => $settingsContent['author-name'] ?? '',
+        "image" => !empty($settingsContent['author-img']) ? $currentDomain . '/' . $settingsContent['author-img'] : '',
+        "jobTitle" => "Casino Expert"
+    ],
+    "publisher" => [
+        "@id" => $currentUrl . "#organization"
+    ]
+];
+
+// 5. Review
+$schemaGraph[] = [
+    "@type" => "Review",
+    "itemReviewed" => [
+        "@id" => $currentUrl . "#brand-casino-entity"
+    ],
+    "reviewRating" => [
+        "@type" => "Rating",
+        "ratingValue" => "5",
+        "bestRating" => "5"
+    ],
+    "author" => [
+        "@id" => $currentUrl . "#organization"
+    ],
+    "name" => $seo['title'],
+    "reviewBody" => $seo['description'],
+    "datePublished" => $settingsContent['published-date'] ?? date('Y-m-d')
+];
+
+// 6. Offer (if bonus fields are filled)
+if (!empty($settingsContent['bonus-name']) || !empty($settingsContent['bonus-description'])) {
+    $schemaGraph[] = [
+        "@type" => "Offer",
+        "@id" => $currentUrl . "#offer",
+        "name" => $settingsContent['bonus-name'] ?? '',
+        "description" => $settingsContent['bonus-description'] ?? '',
+        "url" => $settingsContent['bonus-url'] ?? $currentUrl,
+        "price" => $settingsContent['bonus-price'] ?? '',
+        "priceCurrency" => $settingsContent['bonus-currency'] ?? 'EUR',
+        "availability" => "https://schema.org/InStock",
+        "eligibleCustomerType" => "https://schema.org/NewCustomer",
+        "offeredBy" => [
+            "@id" => $currentUrl . "#organization"
+        ]
+    ];
+}
+
+// 7. BreadcrumbList
+$schemaGraph[] = [
+    "@type" => "BreadcrumbList",
+    "itemListElement" => [
+        [
+            "@type" => "ListItem",
+            "position" => 1,
+            "name" => $settingsContent['title-h1'],
+            "item" => $currentUrl
+        ]
+    ]
+];
+
+// 8. FAQPage
+if (!empty($faqItems)) {
+    $schemaGraph[] = [
+        "@type" => "FAQPage",
+        "mainEntityOfPage" => [
+            "@id" => $currentUrl . "#webpage"
+        ],
+        "mainEntity" => array_map(function ($item) {
+            return [
+                "@type" => "Question",
+                "name" => htmlspecialchars($item['question']),
+                "acceptedAnswer" => [
+                    "@type" => "Answer",
+                    "text" => htmlspecialchars($item['answer'])
+                ]
+            ];
+        }, $faqItems)
+    ];
+}
+
+// 9. SoftwareApplication (if app fields are filled)
+if (!empty($settingsContent['app-download-url']) || !empty($settingsContent['app-install-url'])) {
+    $schemaGraph[] = [
+        "@type" => "SoftwareApplication",
+        "@id" => $currentUrl . "#app",
+        "name" => $settingsContent['title-h1'] . " App",
+        "operatingSystem" => "Android, iOS",
+        "applicationCategory" => "GameApplication",
+        "provider" => [
+            "@id" => $currentUrl . "#organization"
+        ],
+        "offers" => [
+            "@type" => "Offer",
+            "price" => "0",
+            "priceCurrency" => "EUR",
+            "availability" => "https://schema.org/InStock"
+        ],
+        "downloadUrl" => $settingsContent['app-download-url'] ?? '',
+        "installUrl" => $settingsContent['app-install-url'] ?? '',
+        "aggregateRating" => [
+            "@type" => "AggregateRating",
+            "ratingValue" => "5",
+            "ratingCount" => $settingsContent['app-rating-count'] ?? "99",
+            "worstRating" => "1",
+            "bestRating" => "5"
+        ],
+        "author" => [
+            "@id" => $currentUrl . "#organization"
+        ]
+    ];
+}
+
+// Generate final JSON-LD
+$schemaMarkup = [
+    "@context" => "https://schema.org",
+    "@graph" => array_filter($schemaGraph) // Remove null entries
+];
+
+echo json_encode($schemaMarkup, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+?>

+ 8 - 0
src/Enums/PageLayout.php

@@ -50,6 +50,14 @@ enum PageLayout: string
         };
     }
 
+    public function viewPath(): string
+    {
+        return match($this) {
+            self::Default => 'pages/default.php',
+            self::FrontPage => 'home.php',
+        };
+    }
+
     public function description(): string
     {
         return match($this) {

+ 1 - 1
src/Pages/admin/authors.php

@@ -4,7 +4,7 @@ use App\Models\Author;
 
 $authorModel = new Author();
 $action = $_GET['action'] ?? 'list';
-$id = $_GET['id'] ?? null;
+$id = $_GET['id'] ?? $_POST['id'] ?? null;
 
 // Handle form submissions
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {

+ 1 - 3
src/Pages/admin/pages.php

@@ -9,12 +9,11 @@ $pageModel = new Page();
 $seoMetaModel = new SeoMeta();
 $faqModel = new Faq();
 $action = $_GET['action'] ?? 'list';
-$id = $_GET['id'] ?? null;
+$id = $_GET['id'] ?? $_POST['id'] ?? null;
 
 // Handle form submissions
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
     $data = $_POST;
-    
     // Generate slug from name if not provided
     if (empty($data['slug']) && !empty($data['name'])) {
         $data['slug'] = getSlug($data['name']);
@@ -30,7 +29,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
     
     // Get action from form data
     $formAction = $data['action'] ?? $action;
-    
     try {
         switch ($formAction) {
             case 'create':

+ 1 - 0
src/Pages/home.php

@@ -81,6 +81,7 @@ return ViewRender('home.php', [
     'faqItems' => $faqItems,
     'faqTitle' => $faqs['title'] ?? '',
     'pageContent' => $pageContent,
+    'pageData' => $homepage,
     'topContent' => $topContent,
     'currentDomain' => $currentDomain,
     'currentUrl' => $currentUrl,

+ 64 - 0
src/Pages/page.php

@@ -0,0 +1,64 @@
+<?php
+
+use App\Settings;
+use App\Models\Page;
+use App\Models\SeoMeta;
+use App\Models\Faq;
+
+// Получение страницы по slug
+$pageModel = new Page();
+$seoMetaModel = new SeoMeta();
+$faqModel = new Faq();
+
+// Получаем slug из URL
+$slug = $slug ?? $_GET['slug'] ?? '';
+
+// Находим страницу по slug
+$currentPage = \db()->fetchOne("SELECT * FROM pages WHERE slug = ? AND is_homepage = 0", [$slug]);
+
+// Если страница не найдена, возвращаем 404
+if (!$currentPage) {
+    http_response_code(404);
+    die('404 - Page not found');
+}
+
+// Получаем данные из настроек
+$content = (new Settings('content'))->getAll();
+
+// Получаем SEO данные для страницы
+$seo = $seoMetaModel->getForRecord('page', $currentPage['id']) ?: [];
+
+// Контент страницы
+$pageContent = $currentPage['content'] ?? '';
+
+// Получаем топ контент если есть
+$topContent = $currentPage['top_content'] ?? '';
+
+// Получаем FAQ для этой страницы
+$faqItems = $faqModel->getByMorphable('page', $currentPage['id']);
+
+// Данные для домена
+$currentDomain = getCurrentDomain();
+$currentUrl = getCurrentUrl();
+
+// Определение типа пользователя
+$ip = getUserIp();
+$isSearchBot = preg_match('/bot|crawl|slurp|spider/i', $_SERVER['HTTP_USER_AGENT'] ?? '');
+
+// Определяем лейаут страницы
+$layout = \App\Enums\PageLayout::tryFrom($currentPage['layout'] ?? 'default') ?? \App\Enums\PageLayout::Default;
+$layoutFile = $layout->viewPath();
+
+return ViewRender($layoutFile, [
+    'content' => $content,
+    'seo' => $seo,
+    'faqItems' => $faqItems,
+    'pageContent' => $pageContent,
+    'pageData' => $currentPage,
+    'topContent' => $topContent,
+    'currentDomain' => $currentDomain,
+    'currentUrl' => $currentUrl,
+    'isHomepage' => false,
+    'isSearchBot' => $isSearchBot,
+    'userIp' => $ip,
+]);