WebOrbiton
v1.0.0.1

StocketBase

123 lines · 6.4 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. if (count(get_included_files()) === 1) {
  6. http_response_code(403);
  7. exit;
  8. }
  9. ​
  10. $db = Database::site();
  11. $isAdmin = Auth::hasRoleAtLeast(Auth::ROLE_STORE_OWNER);
  12. ​
  13. $statement = $isAdmin
  14. ? $db->query('SELECT p.id, p.title, p.trashed_status, p.deleted_at, t.display_name FROM products p LEFT JOIN team_accounts t ON t.id = p.created_by WHERE p.deleted_at IS NOT NULL ORDER BY p.deleted_at DESC')
  15. : $db->prepare('SELECT p.id, p.title, p.trashed_status, p.deleted_at, t.display_name FROM products p LEFT JOIN team_accounts t ON t.id = p.created_by WHERE p.deleted_at IS NOT NULL AND p.created_by = :id ORDER BY p.deleted_at DESC');
  16. ​
  17. if (!$isAdmin) {
  18. $statement->execute(['id' => $currentUser['id']]);
  19. }
  20. ​
  21. $trashed = $statement->fetchAll();
  22. ​
  23. $trashedFileCounts = [];
  24. if (!empty($trashed)) {
  25. $trashedIds = array_map(static fn(array $row): int => (int) $row['id'], $trashed);
  26. $placeholders = implode(',', array_fill(0, count($trashedIds), '?'));
  27. try {
  28. $countStatement = $db->prepare("SELECT product_id, COUNT(*) AS files FROM product_files WHERE product_id IN ({$placeholders}) GROUP BY product_id");
  29. $countStatement->execute($trashedIds);
  30. foreach ($countStatement->fetchAll() as $countRow) {
  31. $trashedFileCounts[(int) $countRow['product_id']] = (int) $countRow['files'];
  32. }
  33. } catch (PDOException $exception) {
  34. $trashedFileCounts = [];
  35. }
  36. }
  37. $trashHasFiles = array_sum($trashedFileCounts) > 0;
  38. ​
  39. $renderFileDialog = static function (string $dialogId, string $action, ?int $productId, string $question, int $fileCount): string {
  40. return '<dialog class="dash-dialog" id="' . htmlspecialchars($dialogId) . '">'
  41. . '<form method="post">'
  42. . Csrf::field()
  43. . '<input type="hidden" name="action" value="' . htmlspecialchars($action) . '">'
  44. . ($productId !== null ? '<input type="hidden" name="product_id" value="' . $productId . '">' : '')
  45. . '<h2 class="dash-dialog-title">' . Icons::icon('download', 'icon icon-sm') . htmlspecialchars($question) . '</h2>'
  46. . '<p class="dash-dialog-text">' . ($productId === null ? 'These products will be gone for good. ' : 'This product will be gone for good. ') . 'Files attached: ' . $fileCount . '.</p>'
  47. . '<p class="dash-dialog-text"><strong>What should happen to the files?</strong></p>'
  48. . '<ul class="dash-dialog-list">'
  49. . '<li><strong>Keep the files</strong> if people who already bought it should still be able to download. They stay under Files.</li>'
  50. . '<li><strong>Delete the files</strong> to erase them for everyone, including past buyers. This can\'t be undone.</li>'
  51. . '</ul>'
  52. . '<div class="dash-dialog-actions">'
  53. . '<button type="button" class="dash-btn" onclick="this.closest(\'dialog\').close();">Cancel</button>'
  54. . '<button type="submit" name="file_action" value="keep" class="dash-btn dash-btn-primary">Delete, keep the files</button>'
  55. . '<button type="submit" name="file_action" value="delete" class="dash-btn dash-btn-danger">Delete everything</button>'
  56. . '</div>'
  57. . '</form>'
  58. . '</dialog>';
  59. };
  60. ​
  61. ?>
  62. <div class="dash-header-row">
  63. <h1 class="dash-title"><?= Icons::icon('trash', 'icon icon-lg') ?>Trash</h1>
  64. <a href="dashboard.php?view=products" class="dash-btn"><?= Icons::icon('grid', 'icon icon-sm') ?>Back to products</a>
  65. <?php if (!empty($trashed)): ?>
  66. <?php if ($trashHasFiles): ?>
  67. <button type="button" class="dash-btn dash-btn-danger" onclick="document.getElementById('trash-dialog-all').showModal();"><?= Icons::icon('trash', 'icon icon-sm') ?>Empty trash</button>
  68. <?= $renderFileDialog('trash-dialog-all', 'empty_trash', null, 'Empty the trash?', array_sum($trashedFileCounts)) ?>
  69. <?php else: ?>
  70. <form method="post" style="display:inline;" onsubmit="return confirm('Empty the trash? Everything in it will be gone for good.');">
  71. <?= Csrf::field() ?>
  72. <input type="hidden" name="action" value="empty_trash">
  73. <button type="submit" class="dash-btn dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Empty trash</button>
  74. </form>
  75. <?php endif; ?>
  76. <?php endif; ?>
  77. </div>
  78. ​
  79. <table class="dash-table">
  80. <thead>
  81. <tr>
  82. <th><?= Icons::icon('heading', 'icon icon-sm') ?>Title</th>
  83. <th><?= Icons::icon('user', 'icon icon-sm') ?>Added by</th>
  84. <th><?= Icons::icon('flag', 'icon icon-sm') ?>Was</th>
  85. <th><?= Icons::icon('clock', 'icon icon-sm') ?>Deleted</th>
  86. <th></th>
  87. </tr>
  88. </thead>
  89. <tbody>
  90. <?php foreach ($trashed as $product): ?>
  91. <tr>
  92. <td><?= htmlspecialchars($product['title']) ?><?php if (($trashedFileCounts[(int) $product['id']] ?? 0) > 0): ?> <span class="status-pill status-scheduled"><?= (int) $trashedFileCounts[(int) $product['id']] ?> file<?= $trashedFileCounts[(int) $product['id']] === 1 ? '' : 's' ?></span><?php endif; ?></td>
  93. <td><?= htmlspecialchars((string) ($product['display_name'] ?? 'System')) ?></td>
  94. <td><?= htmlspecialchars((string) ($product['trashed_status'] ?? 'draft')) ?></td>
  95. <td><?= htmlspecialchars($product['deleted_at']) ?></td>
  96. <td class="dash-table-actions">
  97. <form method="post" style="display:inline;">
  98. <?= Csrf::field() ?>
  99. <input type="hidden" name="action" value="restore_product">
  100. <input type="hidden" name="product_id" value="<?= (int) $product['id'] ?>">
  101. <button type="submit" class="dash-btn-small dash-btn-success"><?= Icons::icon('refresh', 'icon icon-sm') ?>Restore</button>
  102. </form>
  103. <?php $productFileCount = $trashedFileCounts[(int) $product['id']] ?? 0; ?>
  104. <?php if ($productFileCount > 0): ?>
  105. <button type="button" class="dash-btn-small dash-btn-danger" onclick="document.getElementById('trash-dialog-<?= (int) $product['id'] ?>').showModal();"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete forever</button>
  106. <?= $renderFileDialog('trash-dialog-' . (int) $product['id'], 'purge_product', (int) $product['id'], 'Delete "' . $product['title'] . '" forever?', $productFileCount) ?>
  107. <?php else: ?>
  108. <form method="post" style="display:inline;" onsubmit="return confirm('Delete this product for good? This can\'t be undone.');">
  109. <?= Csrf::field() ?>
  110. <input type="hidden" name="action" value="purge_product">
  111. <input type="hidden" name="product_id" value="<?= (int) $product['id'] ?>">
  112. <button type="submit" class="dash-btn-small dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete forever</button>
  113. </form>
  114. <?php endif; ?>
  115. </td>
  116. </tr>
  117. <?php endforeach; ?>
  118. <?php if (empty($trashed)): ?>
  119. <tr><td colspan="5">The trash is empty. Nice and tidy.</td></tr>
  120. <?php endif; ?>
  121. </tbody>
  122. </table>
  123. ​