gallery.php

main 191 lines · 5.9 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2/**
3 * Title: Gallery
4 * Icon: fa-picture-o
5 * Group: Content
6 * Order: 30
7 * Description: Moderate player screenshot submissions.
8 */
9
10if (!defined('ACP_ROOT')) {
11 http_response_code(403);
12 die('Direct access denied.');
13}
14
15function acp_gallery_rebuild_cache(): void {
16 $cache = new Cache('engine/cache/gallery');
17 $images = fetchImages(2);
18
19 $data = [];
20 if (is_array($images)) {
21 foreach ($images as $image) {
22 $data[] = [
23 'title' => $image['title'] ?? '',
24 'desc' => $image['desc'] ?? '',
25 'date' => $image['date'] ?? '',
26 'image' => $image['image'] ?? '',
27 ];
28 }
29 }
30
31 $cache->setContent($data);
32 $cache->save();
33}
34
35// ---------------------------------------------------------------------------
36// Actions
37// ---------------------------------------------------------------------------
38if ($_SERVER['REQUEST_METHOD'] === 'POST') {
39
40 $do = (string)($_POST['do'] ?? '');
41 $id = intv($_POST['id'] ?? 0);
42
43 if ($id > 0) {
44 // Soft delete: hidden from the public gallery but kept on record.
45 if ($do === 'delete') {
46 updateImage($id, 3);
47 acp_gallery_rebuild_cache();
48 acp_log('gallery.hide', '#' . $id);
49 acp_flash_success(t('acp.gal.hidden', ['id' => $id]));
50 acp_redirect('gallery');
51 }
52
53 // Accept, or recover a soft-deleted image.
54 if ($do === 'accept') {
55 updateImage($id, 2);
56 acp_gallery_rebuild_cache();
57 acp_log('gallery.accept', '#' . $id);
58 acp_flash_success(t('acp.gal.public', ['id' => $id]));
59 acp_redirect('gallery');
60 }
61
62 // Hard delete: drop it at imgur too, then remove the row.
63 if ($do === 'remove') {
64 $delhash = (string)($_POST['delhash'] ?? '');
65 $imgurClientID = (string)($config['gallery']['Client ID'] ?? '');
66
67 if ($delhash !== '' && $imgurClientID !== '' && function_exists('curl_init')) {
68 $ch = curl_init();
69 curl_setopt($ch, CURLOPT_URL, 'https://api.imgur.com/3/image/' . rawurlencode($delhash));
70 curl_setopt($ch, CURLOPT_HEADER, false);
71 curl_setopt($ch, CURLOPT_POST, true);
72 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
73 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
74 curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Client-ID {$imgurClientID}"]);
75
76 $result = curl_exec($ch);
77 if ($result === false) {
78 error_log('cURL error (imgur delete): ' . curl_error($ch));
79 }
80 curl_close($ch);
81 }
82
83 db()->execute("DELETE FROM `znote_images` WHERE `id` = ? LIMIT 1;", [$id]);
84 acp_gallery_rebuild_cache();
85 acp_log('gallery.remove', '#' . $id);
86 acp_flash_success(t('acp.gal.removed', ['id' => $id]));
87 acp_redirect('gallery');
88 }
89 }
90
91 acp_flash_error(t('acp.gal.unknown_action'));
92 acp_redirect('gallery');
93}
94
95/**
96 * One moderation section: heading, count, and a card per image.
97 *
98 * @param array<int, array<string, mixed>>|false $images
99 * @param array<int, array{do: string, label: string, icon: string, class: string, confirm?: string}> $actions
100 */
101function acp_gallery_section($images, string $title, string $subtitle, array $actions, string $emptyText): void {
102 $images = is_array($images) ? $images : [];
103 ?>
104 <section class="acp-card">
105 <header class="acp-card-head">
106 <h2><?= h($title) ?></h2>
107 <p><?= h($subtitle) ?></p>
108 <span class="acp-pill acp-pill--grey"><?= count($images) ?></span>
109 </header>
110 <div class="acp-card-body">
111 <?php if (!$images): ?>
112 <?php acp_empty($emptyText, 'fa-picture-o'); ?>
113 <?php else: ?>
114 <div class="acp-media">
115 <?php foreach ($images as $image):
116 $id = (int)($image['id'] ?? 0);
117 $imageUrl = (string)($image['image'] ?? '');
118 $delhash = (string)($image['delhash'] ?? '');
119 $desc = str_replace(['\\r', '\\n'], ['', '<br>'], h((string)($image['desc'] ?? '')));
120 ?>
121 <article class="acp-media-item">
122 <a href="<?= h($imageUrl) ?>" target="_blank" rel="noopener">
123 <img src="<?= h($imageUrl) ?>" alt="<?= h((string)($image['title'] ?? '')) ?>" loading="lazy">
124 </a>
125 <div class="acp-media-body">
126 <h3><?= h((string)($image['title'] ?? '')) ?></h3>
127 <p><?= $desc ?></p>
128 </div>
129 <div class="acp-media-foot">
130 <?php foreach ($actions as $action): ?>
131 <form class="acp-inline-form" method="post"
132 <?= isset($action['confirm']) ? 'data-confirm="' . h($action['confirm']) . '"' : '' ?>>
133 <?= acp_csrf_field() ?>
134 <input type="hidden" name="do" value="<?= h($action['do']) ?>">
135 <input type="hidden" name="id" value="<?= $id ?>">
136 <?php if ($action['do'] === 'remove'): ?>
137 <input type="hidden" name="delhash" value="<?= h($delhash) ?>">
138 <?php endif; ?>
139 <button class="acp-btn <?= h($action['class']) ?> acp-btn--sm" type="submit">
140 <i class="fa <?= h($action['icon']) ?>"></i> <?= h($action['label']) ?>
141 </button>
142 </form>
143 <?php endforeach; ?>
144 </div>
145 </article>
146 <?php endforeach; ?>
147 </div>
148 <?php endif; ?>
149 </div>
150 </section>
151 <?php
152}
153
154acp_gallery_section(
155 fetchImages(1),
156 t('acp.gal.pending_title'),
157 t('acp.gal.pending_sub'),
158 [
159 ['do' => 'accept', 'label' => t('acp.gal.approve'), 'icon' => 'fa-check', 'class' => 'acp-btn--green'],
160 ['do' => 'delete', 'label' => t('acp.gal.reject'), 'icon' => 'fa-times', 'class' => 'acp-btn--red'],
161 ],
162 t('acp.gal.pending_empty')
163);
164
165acp_gallery_section(
166 fetchImages(2),
167 t('acp.gal.public_title'),
168 t('acp.gal.public_sub'),
169 [
170 ['do' => 'delete', 'label' => t('acp.gal.hide'), 'icon' => 'fa-eye-slash', 'class' => 'acp-btn--amber'],
171 ],
172 t('acp.gal.public_empty')
173);
174
175acp_gallery_section(
176 fetchImages(3),
177 t('acp.gal.hidden_title'),
178 t('acp.gal.hidden_sub'),
179 [
180 ['do' => 'accept', 'label' => t('acp.gal.recover'), 'icon' => 'fa-undo', 'class' => 'acp-btn--green'],
181 [
182 'do' => 'remove',
183 'label' => t('acp.gal.delete_forever'),
184 'icon' => 'fa-trash',
185 'class' => 'acp-btn--red',
186 'confirm' => t('acp.gal.delete_confirm'),
187 ],
188 ],
189 t('acp.gal.hidden_empty')
190);
191
Top