WebOrbiton
v1.0.0.1

StocketBase

267 lines · 11.5 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/auth.php';
  8. require_once __DIR__ . '/includes/csrf.php';
  9. require_once __DIR__ . '/includes/block-editor.php';
  10. require_once __DIR__ . '/includes/site-front.php';
  11. require_once __DIR__ . '/includes/activity-log.php';
  12. require_once __DIR__ . '/includes/indexnow.php';
  13. require_once __DIR__ . '/includes/avatar.php';
  14. require_once __DIR__ . '/includes/product-versions.php';
  15. require_once __DIR__ . '/includes/tags.php';
  16. require_once __DIR__ . '/includes/login-throttle.php';
  17. require_once __DIR__ . '/includes/two-factor.php';
  18. ​
  19. Auth::boot();
  20. Auth::requireLogin();
  21. ​
  22. $currentUser = Auth::user();
  23. $currentRole = Auth::role();
  24. SiteFront::settings();
  25. $view = $_GET['view'] ?? 'overview';
  26. $allowedViews = ['overview', 'products', 'product-edit', 'categories', 'orders', 'order-detail', 'customers', 'customer-detail', 'discounts', 'reviews', 'pages', 'stats', 'security', 'trash'];
  27. if (!in_array($view, $allowedViews, true)) {
  28. $view = 'overview';
  29. }
  30. ​
  31. $flashMessage = null;
  32. $flashType = 'success';
  33. ​
  34. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  35. if (empty($_POST) && (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
  36. $flashMessage = 'That file is too big for your server. The limit is ' . ini_get('upload_max_filesize') . ', ask your host if you need more.';
  37. $flashType = 'error';
  38. } elseif (!Csrf::verify($_POST['csrf_token'] ?? null)) {
  39. $flashMessage = 'Your session expired. Please try again.';
  40. $flashType = 'error';
  41. } else {
  42. $action = $_POST['action'] ?? '';
  43. ​
  44. if (in_array($action, ['upload_product_file', 'delete_product_file', 'save_product_file_labels'], true)) {
  45. [$flashMessage, $flashType, $fileProductId] = require __DIR__ . '/dashboard-actions/product-files.php';
  46. if ($flashType === 'success' && $fileProductId !== null) {
  47. header('Location: dashboard.php?view=product-edit&id=' . (int) $fileProductId . '&saved=1#product-files');
  48. exit;
  49. }
  50. }
  51. ​
  52. if ($action === 'save_product') {
  53. [$flashMessage, $flashType, $redirectId] = require __DIR__ . '/dashboard-actions/save-product.php';
  54. if ($redirectId !== null) {
  55. ActivityLog::record('product.save', 'product', (int) $redirectId, trim((string) ($_POST['title'] ?? '')) . ' [' . (string) ($_POST['publish_action'] ?? '') . ']');
  56. ProductVersions::snapshot((int) $redirectId, (int) $currentUser['id'], (string) $currentUser['display_name']);
  57. ​
  58. if (ProductTags::isEnabled() && isset($_POST['tags'])) {
  59. $taggedProduct = Database::site()->prepare('SELECT created_by FROM products WHERE id = :id');
  60. $taggedProduct->execute(['id' => (int) $redirectId]);
  61. $taggedAuthor = $taggedProduct->fetchColumn();
  62. ​
  63. if ($taggedAuthor !== false && ((int) $taggedAuthor === (int) $currentUser['id'] || Auth::hasRoleAtLeast(Auth::ROLE_STORE_OWNER))) {
  64. ProductTags::sync((int) $redirectId, ProductTags::parse((string) $_POST['tags']));
  65. }
  66. }
  67. ​
  68. IndexNow::notifyProduct((int) $redirectId);
  69. header('Location: dashboard.php?view=product-edit&id=' . $redirectId . '&saved=1');
  70. exit;
  71. }
  72. }
  73. ​
  74. if ($action === 'restore_version') {
  75. [$flashMessage, $flashType, $restoredId] = require __DIR__ . '/dashboard-actions/restore-version.php';
  76. if ($restoredId !== null) {
  77. IndexNow::notifyProduct((int) $restoredId);
  78. header('Location: dashboard.php?view=product-edit&id=' . $restoredId . '&saved=1');
  79. exit;
  80. }
  81. }
  82. ​
  83. if (in_array($action, ['restore_product', 'purge_product', 'empty_trash'], true)) {
  84. $trashedId = require __DIR__ . '/dashboard-actions/trash-product.php';
  85. if ($action === 'restore_product' && $trashedId > 0) {
  86. IndexNow::notifyProduct($trashedId);
  87. }
  88. header('Location: dashboard.php?view=' . ($action === 'restore_product' && $trashedId > 0 ? 'products' : 'trash') . '&saved=1');
  89. exit;
  90. }
  91. ​
  92. if (in_array($action, ['totp_begin', 'totp_cancel', 'totp_confirm', 'totp_disable', 'totp_regenerate'], true)) {
  93. [$flashMessage, $flashType, $securityDone] = require __DIR__ . '/dashboard-actions/security.php';
  94. if ($securityDone) {
  95. header('Location: dashboard.php?view=security');
  96. exit;
  97. }
  98. }
  99. ​
  100. if ($action === 'delete_product') {
  101. $deletedProduct = Database::site()->prepare('SELECT title, slug, status FROM products WHERE id = :id');
  102. $deletedProduct->execute(['id' => (int) ($_POST['product_id'] ?? 0)]);
  103. $deletedProduct = $deletedProduct->fetch() ?: [];
  104. require __DIR__ . '/dashboard-actions/delete-product.php';
  105. ActivityLog::record('product.trash', 'product', (int) ($_POST['product_id'] ?? 0), (string) ($deletedProduct['title'] ?? ''));
  106. if (($deletedProduct['status'] ?? '') === 'published') {
  107. IndexNow::notifyRemoved((string) $deletedProduct['slug'], (int) ($_POST['product_id'] ?? 0));
  108. }
  109. header('Location: dashboard.php?view=products&deleted=1');
  110. exit;
  111. }
  112. ​
  113. if ($action === 'moderate_product') {
  114. require __DIR__ . '/dashboard-actions/moderate-product.php';
  115. ActivityLog::record('product.moderate', 'product', (int) ($_POST['product_id'] ?? 0), (string) ($_POST['decision'] ?? ''));
  116. IndexNow::notifyProduct((int) ($_POST['product_id'] ?? 0));
  117. header('Location: dashboard.php?view=products&moderated=1');
  118. exit;
  119. }
  120. ​
  121. if ($action === 'save_category') {
  122. require __DIR__ . '/dashboard-actions/save-category.php';
  123. ActivityLog::record('category.save', 'category', (int) ($_POST['category_id'] ?? 0) ?: null, trim((string) ($_POST['name'] ?? '')));
  124. header('Location: dashboard.php?view=categories&saved=1');
  125. exit;
  126. }
  127. ​
  128. if ($action === 'delete_category') {
  129. require __DIR__ . '/dashboard-actions/delete-category.php';
  130. ActivityLog::record('category.delete', 'category', (int) ($_POST['category_id'] ?? 0));
  131. header('Location: dashboard.php?view=categories&deleted=1');
  132. exit;
  133. }
  134. ​
  135. if ($action === 'toggle_category_menu') {
  136. require __DIR__ . '/dashboard-actions/toggle-category-menu.php';
  137. header('Location: dashboard.php?view=categories');
  138. exit;
  139. }
  140. ​
  141. if ($action === 'save_page') {
  142. require __DIR__ . '/dashboard-actions/save-page.php';
  143. ActivityLog::record('page.save', 'page', (int) ($_POST['page_id'] ?? 0) ?: null, trim((string) ($_POST['title'] ?? '')));
  144. header('Location: dashboard.php?view=pages&saved=1');
  145. exit;
  146. }
  147. ​
  148. if ($action === 'delete_page') {
  149. require __DIR__ . '/dashboard-actions/delete-page.php';
  150. ActivityLog::record('page.delete', 'page', (int) ($_POST['page_id'] ?? 0));
  151. header('Location: dashboard.php?view=pages&deleted=1');
  152. exit;
  153. }
  154. ​
  155. if ($action === 'update_order_status') {
  156. [$flashMessage, $flashType, $orderId] = require __DIR__ . '/dashboard-actions/save-order-status.php';
  157. if ($orderId !== null && $flashType === 'success') {
  158. ActivityLog::record('order.status', 'order', (int) $orderId, (string) ($_POST['status'] ?? ''));
  159. header('Location: dashboard.php?view=order-detail&id=' . $orderId . '&saved=1');
  160. exit;
  161. }
  162. }
  163. ​
  164. if ($action === 'refund_order') {
  165. [$flashMessage, $flashType, $orderId] = require __DIR__ . '/dashboard-actions/refund-order.php';
  166. if ($orderId !== null && $flashType === 'success') {
  167. ActivityLog::record('order.refund', 'order', (int) $orderId);
  168. header('Location: dashboard.php?view=order-detail&id=' . $orderId . '&saved=1');
  169. exit;
  170. }
  171. }
  172. ​
  173. if ($action === 'save_discount') {
  174. [$flashMessage, $flashType, $discountSaved] = require __DIR__ . '/dashboard-actions/save-discount.php';
  175. if ($discountSaved) {
  176. ActivityLog::record('discount.save', 'discount', (int) ($_POST['discount_id'] ?? 0) ?: null, trim((string) ($_POST['code'] ?? '')));
  177. header('Location: dashboard.php?view=discounts&saved=1');
  178. exit;
  179. }
  180. }
  181. ​
  182. if ($action === 'delete_discount') {
  183. require __DIR__ . '/dashboard-actions/delete-discount.php';
  184. ActivityLog::record('discount.delete', 'discount', (int) ($_POST['discount_id'] ?? 0));
  185. header('Location: dashboard.php?view=discounts&deleted=1');
  186. exit;
  187. }
  188. ​
  189. if ($action === 'moderate_review') {
  190. require __DIR__ . '/dashboard-actions/moderate-review.php';
  191. header('Location: dashboard.php?view=reviews&moderated=1');
  192. exit;
  193. }
  194. }
  195. }
  196. ​
  197. $dashView = $view;
  198. $dashPageTitle = 'Dashboard';
  199. $dashLoadBlockEditor = $view === 'product-edit';
  200. ​
  201. require __DIR__ . '/includes/dash-header.php';
  202. ​
  203. ?>
  204. ​
  205. <?php if ($flashMessage !== null): ?>
  206. <div class="dash-flash dash-flash-<?= htmlspecialchars($flashType) ?>"><?= Icons::icon($flashType === 'error' ? 'x' : 'check', 'icon icon-sm') ?><?= htmlspecialchars($flashMessage) ?></div>
  207. <?php endif; ?>
  208. ​
  209. <?php if (isset($_GET['saved'])): ?>
  210. <div class="dash-flash dash-flash-success"><?= Icons::icon('check', 'icon icon-sm') ?>Changes saved.</div>
  211. <?php endif; ?>
  212. <?php if (isset($_GET['deleted'])): ?>
  213. <div class="dash-flash dash-flash-success"><?= Icons::icon('check', 'icon icon-sm') ?>Deleted.</div>
  214. <?php endif; ?>
  215. <?php if (isset($_GET['moderated'])): ?>
  216. <div class="dash-flash dash-flash-success"><?= Icons::icon('check', 'icon icon-sm') ?>Status updated.</div>
  217. <?php endif; ?>
  218. ​
  219. <?php
  220. switch ($view) {
  221. case 'overview':
  222. require __DIR__ . '/dashboard-views/overview.php';
  223. break;
  224. case 'products':
  225. require __DIR__ . '/dashboard-views/products.php';
  226. break;
  227. case 'product-edit':
  228. require __DIR__ . '/dashboard-views/product-edit.php';
  229. break;
  230. case 'categories':
  231. require __DIR__ . '/dashboard-views/categories.php';
  232. break;
  233. case 'orders':
  234. require __DIR__ . '/dashboard-views/orders.php';
  235. break;
  236. case 'order-detail':
  237. require __DIR__ . '/dashboard-views/order-detail.php';
  238. break;
  239. case 'customers':
  240. require __DIR__ . '/dashboard-views/customers.php';
  241. break;
  242. case 'customer-detail':
  243. require __DIR__ . '/dashboard-views/customer-detail.php';
  244. break;
  245. case 'discounts':
  246. require __DIR__ . '/dashboard-views/discounts.php';
  247. break;
  248. case 'reviews':
  249. require __DIR__ . '/dashboard-views/reviews.php';
  250. break;
  251. case 'pages':
  252. require __DIR__ . '/dashboard-views/pages.php';
  253. break;
  254. case 'stats':
  255. require __DIR__ . '/dashboard-views/stats.php';
  256. break;
  257. case 'security':
  258. require __DIR__ . '/dashboard-views/security.php';
  259. break;
  260. case 'trash':
  261. require __DIR__ . '/dashboard-views/trash.php';
  262. break;
  263. }
  264. ?>
  265. ​
  266. <?php require __DIR__ . '/includes/dash-footer.php'; ?>
  267. ​