WebOrbiton
v1.0.0.1

StocketBase

224 lines · 12.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/csrf.php';
  9. require_once __DIR__ . '/includes/language.php';
  10. require_once __DIR__ . '/includes/site-front.php';
  11. require_once __DIR__ . '/includes/currency.php';
  12. ​
  13. UserAuth::boot();
  14. UserAuth::requireLogin();
  15. ​
  16. $reader = UserAuth::user();
  17. $settings = SiteFront::settings();
  18. $flashMessage = null;
  19. ​
  20. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  21. if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
  22. $flashMessage = Language::get('form_security_failed', 'Security check failed, please try again.');
  23. } else {
  24. $flashMessage = require __DIR__ . '/dashboard-actions/save-account.php';
  25. $reader = UserAuth::user();
  26. }
  27. }
  28. ​
  29. $subscriptionsStatement = Database::users()->prepare(
  30. 'SELECT * FROM subscriptions WHERE user_account_id = :id ORDER BY created_at DESC'
  31. );
  32. $subscriptionsStatement->execute(['id' => $reader['id']]);
  33. $subscriptions = $subscriptionsStatement->fetchAll();
  34. ​
  35. $consentsStatement = Database::users()->prepare(
  36. 'SELECT consent_type, is_granted FROM user_consents WHERE user_account_id = :id'
  37. );
  38. $consentsStatement->execute(['id' => $reader['id']]);
  39. $consentRows = $consentsStatement->fetchAll();
  40. $consents = [];
  41. foreach ($consentRows as $row) {
  42. $consents[$row['consent_type']] = (bool) $row['is_granted'];
  43. }
  44. ​
  45. $ordersStatement = Database::users()->prepare(
  46. 'SELECT * FROM orders WHERE user_account_id = :id ORDER BY created_at DESC LIMIT 100'
  47. );
  48. $ordersStatement->execute(['id' => $reader['id']]);
  49. $orders = $ordersStatement->fetchAll();
  50. ​
  51. require_once __DIR__ . '/includes/digital-files.php';
  52. ​
  53. $accountDownloads = [];
  54. $ownedProductIds = DigitalFiles::ownedProductIds((int) $reader['id']);
  55. if (!empty($ownedProductIds)) {
  56. $placeholders = implode(',', array_fill(0, count($ownedProductIds), '?'));
  57. $ownedProductsStatement = Database::site()->prepare("SELECT id, title FROM products WHERE id IN ({$placeholders})");
  58. $ownedProductsStatement->execute($ownedProductIds);
  59. $ownedTitles = array_column($ownedProductsStatement->fetchAll(), 'title', 'id');
  60. ​
  61. foreach ($ownedProductIds as $ownedProductId) {
  62. $ownedFiles = DigitalFiles::filesForProduct($ownedProductId);
  63. if (empty($ownedFiles)) {
  64. continue;
  65. }
  66. $ownedTitle = (string) ($ownedTitles[$ownedProductId] ?? ($ownedFiles[0]['product_title'] ?? ''));
  67. $accountDownloads[] = ['title' => $ownedTitle !== '' ? $ownedTitle : $ownedFiles[0]['original_name'], 'files' => $ownedFiles];
  68. }
  69. usort($accountDownloads, static fn(array $a, array $b): int => strcasecmp($a['title'], $b['title']));
  70. }
  71. ​
  72. $viewOrderId = (int) ($_GET['order'] ?? 0);
  73. $viewOrder = null;
  74. $viewOrderItems = [];
  75. if ($viewOrderId > 0) {
  76. $viewOrderStatement = Database::users()->prepare(
  77. 'SELECT * FROM orders WHERE id = :id AND user_account_id = :account_id LIMIT 1'
  78. );
  79. $viewOrderStatement->execute(['id' => $viewOrderId, 'account_id' => $reader['id']]);
  80. $viewOrder = $viewOrderStatement->fetch();
  81. ​
  82. if ($viewOrder) {
  83. $viewOrderItemsStatement = Database::users()->prepare(
  84. 'SELECT * FROM order_items WHERE order_id = :id ORDER BY id ASC'
  85. );
  86. $viewOrderItemsStatement->execute(['id' => $viewOrderId]);
  87. $viewOrderItems = $viewOrderItemsStatement->fetchAll();
  88. }
  89. }
  90. ​
  91. $pageTitle = Language::get('account_profile', 'Profile') . ' - ' . $settings['site_name'];
  92. ​
  93. require __DIR__ . '/includes/front-header.php';
  94. ​
  95. ?>
  96. <h1 class="article-title"><?= htmlspecialchars(Language::get('account_profile', 'Profile')) ?></h1>
  97. ​
  98. <?php if ($flashMessage !== null): ?>
  99. <p style="color: var(--accent);"><?= htmlspecialchars($flashMessage) ?></p>
  100. <?php endif; ?>
  101. ​
  102. <form method="post" style="max-width:420px;margin-bottom:32px;">
  103. <?= Csrf::field() ?>
  104. <input type="hidden" name="action" value="update_profile">
  105. <label><?= htmlspecialchars(Language::get('auth_display_name', 'Display name')) ?></label>
  106. <input type="text" name="display_name" value="<?= htmlspecialchars($reader['display_name'] ?? '') ?>" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 14px;">
  107. <label><?= htmlspecialchars(Language::get('account_current_password', 'Current password (required to set a new password)')) ?></label>
  108. <input type="password" name="current_password" autocomplete="current-password" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 14px;">
  109. <label><?= htmlspecialchars(Language::get('account_new_password', 'New password (leave blank to keep current)')) ?></label>
  110. <input type="password" name="new_password" minlength="8" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 14px;">
  111. <button type="submit" class="unlock-btn" style="border:none;cursor:pointer;"><?= htmlspecialchars(Language::get('account_save', 'Save changes')) ?></button>
  112. </form>
  113. ​
  114. <h2><?= htmlspecialchars(Language::get('account_orders', 'Order history')) ?></h2>
  115. ​
  116. <?php if ($viewOrder !== null): ?>
  117. <p><a href="account.php">&larr; <?= htmlspecialchars(Language::get('account_back_to_orders', 'Back to order history')) ?></a></p>
  118. <div class="order-summary">
  119. <p><strong><?= htmlspecialchars(Language::get('order_number_label', 'Order number')) ?>:</strong> <?= htmlspecialchars($viewOrder['order_number']) ?></p>
  120. <p><strong><?= htmlspecialchars(Language::get('order_status_label', 'Status')) ?>:</strong> <span class="status-pill status-<?= htmlspecialchars($viewOrder['status']) ?>"><?= htmlspecialchars(Language::get('order_status_' . $viewOrder['status'], ucfirst($viewOrder['status']))) ?></span></p>
  121. <p><strong><?= htmlspecialchars(Language::get('order_placed_label', 'Placed on')) ?>:</strong> <?= htmlspecialchars($viewOrder['created_at']) ?></p>
  122. <?php if (!empty($viewOrder['tracking_number'])): ?>
  123. <p><strong><?= htmlspecialchars(Language::get('order_tracking_label', 'Tracking')) ?>:</strong>
  124. <?php if (!empty($viewOrder['tracking_url'])): ?>
  125. <a href="<?= htmlspecialchars($viewOrder['tracking_url']) ?>" target="_blank" rel="noopener noreferrer"><?= htmlspecialchars($viewOrder['tracking_number']) ?></a>
  126. <?php else: ?>
  127. <?= htmlspecialchars($viewOrder['tracking_number']) ?>
  128. <?php endif; ?>
  129. </p>
  130. <?php endif; ?>
  131. ​
  132. <table class="dash-table">
  133. <thead>
  134. <tr>
  135. <th><?= htmlspecialchars(Language::get('cart_column_product', 'Product')) ?></th>
  136. <th><?= htmlspecialchars(Language::get('cart_column_quantity', 'Quantity')) ?></th>
  137. <th><?= htmlspecialchars(Language::get('cart_column_total', 'Total')) ?></th>
  138. </tr>
  139. </thead>
  140. <tbody>
  141. <?php foreach ($viewOrderItems as $orderItem): ?>
  142. <tr>
  143. <td><?= htmlspecialchars($orderItem['product_title_snapshot']) ?><?= $orderItem['variant_name_snapshot'] !== null ? ' — ' . htmlspecialchars($orderItem['variant_name_snapshot']) : '' ?></td>
  144. <td><?= (int) $orderItem['quantity'] ?></td>
  145. <td><?= htmlspecialchars(Currency::format((int) $orderItem['total_cents'], $viewOrder['currency'])) ?></td>
  146. </tr>
  147. <?php endforeach; ?>
  148. </tbody>
  149. </table>
  150. ​
  151. <div class="cart-summary">
  152. <div class="cart-summary-row"><span><?= htmlspecialchars(Language::get('cart_subtotal', 'Subtotal')) ?></span><span><?= htmlspecialchars(Currency::format((int) $viewOrder['subtotal_cents'], $viewOrder['currency'])) ?></span></div>
  153. <?php if ((int) $viewOrder['discount_cents'] > 0): ?>
  154. <div class="cart-summary-row"><span><?= htmlspecialchars(Language::get('cart_discount', 'Discount')) ?></span><span>-<?= htmlspecialchars(Currency::format((int) $viewOrder['discount_cents'], $viewOrder['currency'])) ?></span></div>
  155. <?php endif; ?>
  156. <div class="cart-summary-row cart-summary-total"><span><?= htmlspecialchars(Language::get('cart_total', 'Total')) ?></span><span><?= htmlspecialchars(Currency::format((int) $viewOrder['total_cents'], $viewOrder['currency'])) ?></span></div>
  157. </div>
  158. </div>
  159. <?php else: ?>
  160. <table class="dash-table">
  161. <thead>
  162. <tr>
  163. <th><?= htmlspecialchars(Language::get('order_number_label', 'Order number')) ?></th>
  164. <th><?= htmlspecialchars(Language::get('order_placed_label', 'Placed on')) ?></th>
  165. <th><?= htmlspecialchars(Language::get('order_status_label', 'Status')) ?></th>
  166. <th><?= htmlspecialchars(Language::get('cart_column_total', 'Total')) ?></th>
  167. <th></th>
  168. </tr>
  169. </thead>
  170. <tbody>
  171. <?php foreach ($orders as $orderRow): ?>
  172. <tr>
  173. <td><?= htmlspecialchars($orderRow['order_number']) ?></td>
  174. <td><?= htmlspecialchars($orderRow['created_at']) ?></td>
  175. <td><span class="status-pill status-<?= htmlspecialchars($orderRow['status']) ?>"><?= htmlspecialchars(Language::get('order_status_' . $orderRow['status'], ucfirst($orderRow['status']))) ?></span></td>
  176. <td><?= htmlspecialchars(Currency::format((int) $orderRow['total_cents'], $orderRow['currency'])) ?></td>
  177. <td><a href="account.php?order=<?= (int) $orderRow['id'] ?>"><?= htmlspecialchars(Language::get('order_view_button', 'View')) ?></a></td>
  178. </tr>
  179. <?php endforeach; ?>
  180. <?php if (empty($orders)): ?>
  181. <tr><td colspan="5"><?= htmlspecialchars(Language::get('account_no_orders', 'You have not placed any orders yet.')) ?></td></tr>
  182. <?php endif; ?>
  183. </tbody>
  184. </table>
  185. <?php endif; ?>
  186. ​
  187. <?php if (!empty($accountDownloads)): ?>
  188. <h2><?= htmlspecialchars(Language::get('account_downloads', 'Downloads')) ?></h2>
  189. <?php foreach ($accountDownloads as $accountDownload): ?>
  190. <h3 style="margin:18px 0 0;font-size:15px;"><?= htmlspecialchars($accountDownload['title']) ?></h3>
  191. <ul class="download-list">
  192. <?php foreach ($accountDownload['files'] as $accountFile): ?>
  193. <li>
  194. <a href="<?= htmlspecialchars(DigitalFiles::downloadUrl((int) $accountFile['id'])) ?>" rel="nofollow"><?= Icons::icon('download', 'icon icon-sm') ?><?= htmlspecialchars(DigitalFiles::displayName($accountFile)) ?></a>
  195. <small><?= htmlspecialchars(DigitalFiles::formatSize((int) $accountFile['size_bytes'])) ?></small>
  196. </li>
  197. <?php endforeach; ?>
  198. </ul>
  199. <?php endforeach; ?>
  200. <?php endif; ?>
  201. ​
  202. <?php if (($settings['pricing_model'] ?? 'per_product') === 'subscription' || !empty($subscriptions)): ?>
  203. <h2><?= htmlspecialchars(Language::get('account_subscriptions', 'Subscriptions')) ?></h2>
  204. <ul>
  205. <?php foreach ($subscriptions as $subscription): ?>
  206. <li><?= htmlspecialchars($subscription['provider']) ?> — <?= htmlspecialchars($subscription['plan_code'] ?? '') ?> (<?= htmlspecialchars(Language::get('subscription_status_' . $subscription['status'], $subscription['status'])) ?>)</li>
  207. <?php endforeach; ?>
  208. <?php if (empty($subscriptions)): ?>
  209. <li><?= htmlspecialchars(Language::get('account_no_subscriptions', 'No active subscriptions.')) ?></li>
  210. <?php endif; ?>
  211. </ul>
  212. <?php endif; ?>
  213. ​
  214. <h2><?= htmlspecialchars(Language::get('account_consents', 'Privacy consents')) ?></h2>
  215. <form method="post" style="max-width:420px;">
  216. <?= Csrf::field() ?>
  217. <input type="hidden" name="action" value="update_consents">
  218. <label><input type="checkbox" name="consent_analytics" <?= !empty($consents['analytics']) ? 'checked' : '' ?>> <?= htmlspecialchars(Language::get('account_consent_analytics', 'Allow analytics tracking')) ?></label>
  219. <label><input type="checkbox" name="consent_ads_personalization" <?= !empty($consents['ads_personalization']) ? 'checked' : '' ?>> <?= htmlspecialchars(Language::get('account_consent_ads', 'Allow personalized ads')) ?></label>
  220. <button type="submit" class="unlock-btn" style="border:none;cursor:pointer;margin-top:14px;"><?= htmlspecialchars(Language::get('account_save', 'Save changes')) ?></button>
  221. </form>
  222. ​
  223. <?php require __DIR__ . '/includes/front-footer.php'; ?>
  224. ​