WebOrbiton
v1.0.0.0

StocketBase

191 lines · 7.3 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/includes/config.php';
  6. require_once __DIR__ . '/includes/database.php';
  7. require_once __DIR__ . '/includes/user-auth.php';
  8. require_once __DIR__ . '/includes/language.php';
  9. require_once __DIR__ . '/includes/site-front.php';
  10. require_once __DIR__ . '/includes/pagination.php';
  11. require_once __DIR__ . '/includes/product-card.php';
  12. ​
  13. UserAuth::boot();
  14. ​
  15. $db = Database::site();
  16. $settings = SiteFront::settings();
  17. ​
  18. function resolveFeaturedProducts(PDO $db, array $settings): array
  19. {
  20. if (($settings['featured_banner_enabled'] ?? '0') !== '1') {
  21. return [];
  22. }
  23. ​
  24. if (($settings['featured_banner_mode'] ?? 'static') === 'static') {
  25. $featuredIds = [];
  26. for ($i = 1; $i <= 5; $i++) {
  27. $id = (int) ($settings['featured_banner_product_' . $i] ?? 0);
  28. if ($id > 0 && !in_array($id, $featuredIds, true)) {
  29. $featuredIds[] = $id;
  30. }
  31. }
  32. ​
  33. if (empty($featuredIds)) {
  34. return [];
  35. }
  36. ​
  37. $placeholders = implode(',', array_fill(0, count($featuredIds), '?'));
  38. $statement = $db->prepare(
  39. "SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.status = 'published' AND p.id IN ($placeholders)"
  40. );
  41. $statement->execute($featuredIds);
  42. $byId = [];
  43. foreach ($statement->fetchAll() as $row) {
  44. $byId[(int) $row['id']] = $row;
  45. }
  46. ​
  47. $result = [];
  48. foreach ($featuredIds as $id) {
  49. if (isset($byId[$id])) {
  50. $result[] = $byId[$id];
  51. }
  52. }
  53. ​
  54. return $result;
  55. }
  56. ​
  57. $windowValue = max(1, min(720, (int) ($settings['featured_banner_auto_window_value'] ?? 24)));
  58. $windowUnit = ($settings['featured_banner_auto_window_unit'] ?? 'hours') === 'minutes' ? 'minutes' : 'hours';
  59. $windowMinutes = $windowUnit === 'hours' ? $windowValue * 60 : $windowValue;
  60. $cutoff = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->modify('-' . $windowMinutes . ' minutes')->format('Y-m-d H:i:s');
  61. ​
  62. $statement = $db->prepare(
  63. "SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id
  64. WHERE p.status = 'published' AND p.published_at >= :cutoff
  65. ORDER BY p.views_count DESC, p.published_at DESC, p.id DESC"
  66. );
  67. $statement->execute(['cutoff' => $cutoff]);
  68. $candidates = $statement->fetchAll();
  69. $categoryMode = $settings['featured_banner_auto_category_mode'] ?? 'mixed';
  70. ​
  71. $result = [];
  72. if ($categoryMode === 'one_per_category') {
  73. $used = [];
  74. foreach ($candidates as $row) {
  75. $key = (string) ($row['category_id'] ?? 'none');
  76. if (isset($used[$key])) {
  77. continue;
  78. }
  79. $used[$key] = true;
  80. $result[] = $row;
  81. if (count($result) === 5) {
  82. break;
  83. }
  84. }
  85. } elseif ($categoryMode === 'mixed') {
  86. $byCategory = [];
  87. foreach ($candidates as $row) {
  88. $key = (string) ($row['category_id'] ?? 'none');
  89. $byCategory[$key][] = $row;
  90. }
  91. while (count($result) < 5 && !empty($byCategory)) {
  92. foreach ($byCategory as $key => $rows) {
  93. if (!empty($rows)) {
  94. $result[] = array_shift($byCategory[$key]);
  95. }
  96. if (empty($byCategory[$key])) {
  97. unset($byCategory[$key]);
  98. }
  99. if (count($result) === 5) {
  100. break;
  101. }
  102. }
  103. }
  104. } else {
  105. $result = array_slice($candidates, 0, 5);
  106. }
  107. ​
  108. return $result;
  109. }
  110. ​
  111. function resolveFeaturedExcludedIds(array $settings, array $featuredProducts): array
  112. {
  113. if (($settings['featured_banner_enabled'] ?? '0') !== '1' || ($settings['featured_banner_hide_duplicates'] ?? '0') !== '1') {
  114. return [];
  115. }
  116. ​
  117. return array_values(array_map(static fn (array $product): int => (int) $product['id'], $featuredProducts));
  118. }
  119. ​
  120. $featuredProducts = resolveFeaturedProducts($db, $settings);
  121. $excludedIds = resolveFeaturedExcludedIds($settings, $featuredProducts);
  122. $excludeSql = empty($excludedIds) ? '' : ' AND p.id NOT IN (' . implode(',', $excludedIds) . ')';
  123. ​
  124. $currentPage = Pagination::currentPage();
  125. $totalProducts = (int) $db->query("SELECT COUNT(*) AS c FROM products p WHERE p.status = 'published'" . $excludeSql)->fetch()['c'];
  126. $isStaticProductLoading = ($settings['product_loading_mode'] ?? 'scroll') === 'static';
  127. $perPage = $isStaticProductLoading
  128. ? max(1, $totalProducts)
  129. : max(1, (int) $settings['products_per_page_home']);
  130. $currentPage = $isStaticProductLoading ? 1 : $currentPage;
  131. $offset = Pagination::offset($currentPage, $perPage);
  132. $totalPages = Pagination::totalPages($totalProducts, $perPage);
  133. ​
  134. $productsStatement = $db->prepare(
  135. "SELECT p.*, c.name AS category_name, c.slug AS category_slug FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.status = 'published'" . $excludeSql . " ORDER BY p.published_at DESC LIMIT :limit OFFSET :offset"
  136. );
  137. $productsStatement->bindValue('limit', $perPage, PDO::PARAM_INT);
  138. $productsStatement->bindValue('offset', $offset, PDO::PARAM_INT);
  139. $productsStatement->execute();
  140. $products = $productsStatement->fetchAll();
  141. ​
  142. $pageTitle = $settings['seo_title'] !== '' ? $settings['seo_title'] : $settings['site_name'];
  143. $pageDescription = $settings['seo_description'] !== '' ? $settings['seo_description'] : $settings['site_description'];
  144. $pageType = 'index';
  145. ​
  146. require __DIR__ . '/includes/front-header.php';
  147. ​
  148. ?>
  149. <?php if (!empty($settings['site_description'])): ?>
  150. <section class="hero-section">
  151. <div class="hero-eyebrow"><?= htmlspecialchars(Language::get('nav_home', 'Home')) ?></div>
  152. <h1 class="hero-title"><?= htmlspecialchars($settings['site_name']) ?></h1>
  153. <p class="hero-subtitle"><?= htmlspecialchars($settings['site_description']) ?></p>
  154. </section>
  155. <?php endif; ?>
  156. ​
  157. <?php foreach (SiteFront::activeAds('homepage-top') as $homepageTopAd): ?>
  158. <?= SiteFront::renderAd($homepageTopAd) ?>
  159. <?php endforeach; ?>
  160. ​
  161. <?php if (($settings['featured_banner_enabled'] ?? '0') === '1'): ?>
  162. <div class="section-heading-row">
  163. <h2 class="section-heading"><?= htmlspecialchars(Language::get('home_featured', 'Featured')) ?></h2>
  164. </div>
  165. ​
  166. <?php if (!empty($featuredProducts)): ?>
  167. <?= renderFeaturedBanner($featuredProducts) ?>
  168. <?php endif; ?>
  169. <?php endif; ?>
  170. ​
  171. <div class="article-grid" id="article-list" data-pagination-source="home">
  172. <?php $catalogTopAds = SiteFront::activeAds('catalog-top'); ?>
  173. <?php foreach ($products as $inFeedIndex => $product): ?>
  174. <?= renderProductCard($product) ?>
  175. <?php if ($inFeedIndex === 2 && !empty($catalogTopAds)): ?>
  176. <?php foreach ($catalogTopAds as $catalogTopAd): ?>
  177. <?= SiteFront::renderAd($catalogTopAd) ?>
  178. <?php endforeach; ?>
  179. <?php endif; ?>
  180. <?php endforeach; ?>
  181. <?php if (empty($products)): ?>
  182. <p><?= htmlspecialchars(Language::get('home_no_products', 'No products published yet.')) ?></p>
  183. <?php endif; ?>
  184. </div>
  185. ​
  186. <div id="pagination-container">
  187. <?= $isStaticProductLoading ? '' : Pagination::render($settings['pagination_style'], 'index.php', $currentPage, $totalPages, [], 'article-list') ?>
  188. </div>
  189. ​
  190. <?php require __DIR__ . '/includes/front-footer.php'; ?>
  191. ​