mass_actions.php

main 240 lines · 8.2 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2/**
3 * Title: Mass Actions
4 * Icon: fa-tasks
5 * Group: Players
6 * Order: 21
7 * Description: Apply points, premium days or a teleport to every account or character at once.
8 */
9
10if (!defined('ACP_ROOT')) {
11 http_response_code(403);
12 die('Direct access denied.');
13}
14
15$accountTotal = acp_count("SELECT COUNT(*) AS `c` FROM `accounts`;");
16
17if ($_SERVER['REQUEST_METHOD'] === 'POST') {
18
19 // -------------------------------------------------------- Mass points
20 if (isset($_POST['points_delta'])) {
21 $delta = intv($_POST['points_delta'] ?? 0);
22
23 if ($delta === 0) {
24 acp_flash_error(t('acp.mpts.zero_value'));
25 acp_redirect('mass_actions');
26 }
27
28 db()->execute("
29 INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `flag`)
30 SELECT `a`.`id`, 0, ?, ''
31 FROM `accounts` `a`
32 LEFT JOIN `znote_accounts` `z` ON `z`.`account_id` = `a`.`id`
33 WHERE `z`.`account_id` IS NULL;
34 ", [time()]);
35
36 $updated = db()->execute("UPDATE `znote_accounts` SET `points` = GREATEST(0, `points` + ?);", [$delta]);
37
38 if ($updated !== false) {
39 acp_log('accounts.mass_points', '', ['delta' => $delta, 'accounts' => $accountTotal]);
40 acp_flash_success(t('acp.mpts.done', ['delta' => ($delta >= 0 ? '+' : '') . $delta, 'n' => $accountTotal]));
41 } else {
42 acp_flash_error(t('acp.mpts.failed'));
43 }
44
45 acp_redirect('mass_actions');
46 }
47
48 // --------------------------------------------------- Mass premium days
49 if (isset($_POST['premium_days'])) {
50 $days = intv($_POST['premium_days'] ?? 0);
51
52 if ($days <= 0) {
53 acp_flash_error(t('acp.mprem.invalid_value'));
54 acp_redirect('mass_actions');
55 }
56
57 $updated = user_accounts_add_premdays_all($days);
58
59 if ($updated !== false) {
60 acp_log('accounts.mass_premium_days', '', ['days' => $days, 'accounts' => $accountTotal]);
61 acp_flash_success(t('acp.mprem.done', ['days' => $days, 'n' => $accountTotal]));
62 } else {
63 acp_flash_error(t('acp.mprem.not_supported'));
64 }
65
66 acp_redirect('mass_actions');
67 }
68
69 // ------------------------------------------------------------ Teleport
70 if (isset($_POST['from'], $_POST['to'])) {
71 $from = (string)$_POST['from'];
72 $to = (string)$_POST['to'];
73
74 $target = '';
75 $fail = false;
76
77 if ($from === 'only') {
78 $target = trim((string)($_POST['player_name'] ?? ''));
79 if ($target === '' || !user_character_exist($target)) {
80 acp_flash_error(t('acp.plr.err_invalid_teleport_char'));
81 $fail = true;
82 }
83 }
84
85 if (!$fail) {
86 $set = null;
87
88 if ($to === 'home') {
89 $set = '`posx`=0, `posy`=0, `posz`=0';
90 } elseif ($to === 'town') {
91 $set = '`posx`=0, `posy`=0, `posz`=0, `town_id`=' . intv($_POST['town'] ?? 0);
92 } elseif ($to === 'xyz') {
93 $set = '`posx`=' . intv($_POST['x'] ?? 0)
94 . ', `posy`=' . intv($_POST['y'] ?? 0)
95 . ', `posz`=' . intv($_POST['z'] ?? 0);
96 }
97
98 // Anything else would have produced "UPDATE players SET " and a
99 // SQL error, so refuse instead of guessing.
100 if ($set === null) {
101 acp_flash_error(t('acp.plr.err_unknown_destination'));
102 } else {
103 if ($from === 'only') {
104 db()->execute("UPDATE `players` SET {$set} WHERE `name` = ?;", [$target]);
105 } else {
106 db()->execute("UPDATE `players` SET {$set};");
107 }
108 acp_log('player.teleport', $from === 'only' ? $target : 'ALL', ['destination' => $to]);
109 acp_flash_success($from === 'only'
110 ? t('acp.plr.tp_one_done')
111 : t('acp.plr.tp_all_done'));
112 }
113 }
114
115 acp_redirect('mass_actions');
116 }
117}
118?>
119
120<div class="acp-stats">
121 <?php acp_stat(t('acp.mpts.stat_accounts'), $accountTotal, 'fa-users', acp_url('accounts'), 'blue'); ?>
122</div>
123
124<div class="acp-grid acp-grid--2">
125
126 <!-- --------------------------------------------------------- Mass points -->
127 <section class="acp-card">
128 <header class="acp-card-head">
129 <h2><?= t('acp.mpts.title') ?></h2>
130 <p><?= t('acp.mpts.sub') ?></p>
131 </header>
132 <div class="acp-card-body">
133 <div class="acp-flash acp-flash--error" style="margin-bottom:16px;">
134 <i class="fa fa-exclamation-triangle"></i>
135 <span><?= t('acp.mpts.warning', ['n' => $accountTotal]) ?></span>
136 </div>
137
138 <form method="post" data-confirm="<?= h(t('acp.mpts.confirm', ['n' => $accountTotal])) ?>">
139 <?= acp_csrf_field() ?>
140 <div class="acp-field">
141 <label class="acp-label" for="points_delta"><?= t('acp.mpts.delta_label') ?></label>
142 <input class="acp-input" id="points_delta" name="points_delta" type="number" step="1" placeholder="<?= h(t('acp.mpts.delta_placeholder')) ?>" required>
143 <p class="acp-hint"><?= t('acp.mpts.delta_help') ?></p>
144 </div>
145 <div class="acp-actions">
146 <button class="acp-btn acp-btn--amber" type="submit"><i class="fa fa-bolt"></i> <?= t('acp.mpts.submit_btn') ?></button>
147 </div>
148 </form>
149 </div>
150 </section>
151
152 <!-- --------------------------------------------------- Mass premium days -->
153 <section class="acp-card">
154 <header class="acp-card-head">
155 <h2><?= t('acp.mprem.title') ?></h2>
156 <p><?= t('acp.mprem.sub') ?></p>
157 </header>
158 <div class="acp-card-body">
159 <div class="acp-flash acp-flash--error" style="margin-bottom:16px;">
160 <i class="fa fa-exclamation-triangle"></i>
161 <span><?= t('acp.mprem.warning', ['n' => $accountTotal]) ?></span>
162 </div>
163
164 <form method="post" data-confirm="<?= h(t('acp.mprem.confirm', ['n' => $accountTotal])) ?>">
165 <?= acp_csrf_field() ?>
166 <div class="acp-field">
167 <label class="acp-label" for="premium_days"><?= t('acp.mprem.days_label') ?></label>
168 <input class="acp-input" id="premium_days" name="premium_days" type="number" min="1" step="1" placeholder="<?= h(t('acp.mprem.days_placeholder')) ?>" required>
169 <p class="acp-hint"><?= t('acp.mprem.days_help') ?></p>
170 </div>
171 <div class="acp-actions">
172 <button class="acp-btn acp-btn--amber" type="submit"><i class="fa fa-star"></i> <?= t('acp.mprem.submit_btn') ?></button>
173 </div>
174 </form>
175 </div>
176 </section>
177
178</div>
179
180<!-- ------------------------------------------------------------ Teleport -->
181<section class="acp-card">
182 <header class="acp-card-head">
183 <h2><?= h(t('acp.plr.teleport_title')) ?></h2>
184 <p><?= h(t('acp.plr.teleport_sub')) ?></p>
185 </header>
186 <div class="acp-card-body">
187 <form method="post" data-confirm="<?= h(t('acp.plr.teleport_confirm')) ?>">
188 <?= acp_csrf_field() ?>
189 <div class="acp-row">
190 <div class="acp-field">
191 <label class="acp-label" for="tp_from"><?= h(t('acp.plr.who_label')) ?></label>
192 <select class="acp-select" id="tp_from" name="from">
193 <option value="only"><?= h(t('acp.plr.tp_one_option')) ?></option>
194 <option value="all"><?= h(t('acp.plr.tp_all_option')) ?></option>
195 </select>
196 </div>
197 <div class="acp-field">
198 <label class="acp-label" for="player_name"><?= h(t('acp.plr.character_label')) ?></label>
199 <input class="acp-input" id="player_name" name="player_name" placeholder="<?= h(t('acp.plr.tp_char_placeholder')) ?>">
200 </div>
201 <div class="acp-field">
202 <label class="acp-label" for="tp_to"><?= h(t('acp.plr.destination_label')) ?></label>
203 <select class="acp-select" id="tp_to" name="to">
204 <option value="home"><?= h(t('acp.plr.dest_home')) ?></option>
205 <option value="town"><?= h(t('acp.plr.dest_town')) ?></option>
206 <option value="xyz"><?= h(t('acp.plr.dest_xyz')) ?></option>
207 </select>
208 </div>
209 </div>
210
211 <div class="acp-row">
212 <div class="acp-field">
213 <label class="acp-label" for="tp_town"><?= h(t('acp.plr.town_label')) ?></label>
214 <select class="acp-select" id="tp_town" name="town">
215 <?php foreach (($config['towns'] ?? []) as $tid => $tname): ?>
216 <option value="<?= (int)$tid ?>"><?= h($tname) ?></option>
217 <?php endforeach; ?>
218 </select>
219 </div>
220 <div class="acp-field">
221 <label class="acp-label" for="tp_x">X</label>
222 <input class="acp-input" id="tp_x" name="x" type="number" value="0">
223 </div>
224 <div class="acp-field">
225 <label class="acp-label" for="tp_y">Y</label>
226 <input class="acp-input" id="tp_y" name="y" type="number" value="0">
227 </div>
228 <div class="acp-field">
229 <label class="acp-label" for="tp_z">Z</label>
230 <input class="acp-input" id="tp_z" name="z" type="number" value="7">
231 </div>
232 </div>
233
234 <div class="acp-actions">
235 <button class="acp-btn" type="submit"><i class="fa fa-location-arrow"></i> <?= h(t('acp.plr.teleport_btn')) ?></button>
236 </div>
237 </form>
238 </div>
239</section>
240
Top