Initial commit

ZnoteX / Commit #5

Commit Initial commit

Alex Alex committed 01/10/2026 09:20 main Full upload
481 files +128,311 -0
A recovery.php +387-0 View file
@@ -0,0 +1,387 @@
1+<?php require_once 'engine/init.php';
2+znote_csrf_protect_public_post();
3+logged_in_redirect();
4+theme_open();
5+if (function_exists('tco_recovery_open')) {
6+ tco_recovery_open();
7+}
8+if ($config['mailserver']['accountRecovery']) {
9+ // Fetch, sanitize and assign POST and GET variables.
10+ $mode = (isset($_GET['mode']) && !empty($_GET['mode'])) ? getValue($_GET['mode'] ?? null) : false;
11+ $email = (isset($_POST['email']) && !empty($_POST['email'])) ? getValue($_POST['email'] ?? null) : false;
12+ $character = (isset($_POST['character']) && !empty($_POST['character'])) ? getValue($_POST['character'] ?? null) : false;
13+ if (!$email && !empty($_POST['email_rcv'])) {
14+ $email = getValue($_POST['email_rcv'] ?? null);
15+ }
16+ if (!$character && !empty($_POST['nick'])) {
17+ $character = getValue($_POST['nick'] ?? null);
18+ }
19+ $password = (isset($_POST['password']) && !empty($_POST['password'])) ? getValue($_POST['password'] ?? null) : false;
20+ $username = (isset($_POST['username']) && !empty($_POST['username'])) ? getValue($_POST['username'] ?? null) : false;
21+ //data_dump($_GET, $_POST, "Posted data.");
22+
23+ if (!empty($_POST)) {
24+ $status = true;
25+ if ($config['use_captcha']) {
26+ if(!verifyGoogleReCaptcha($_POST['g-recaptcha-response'])) {
27+ $status = false;
28+ }
29+ }
30+ if ($status) {
31+ if (isset($_POST['action_type'])) {
32+ $actionType = getValue($_POST['action_type'] ?? '');
33+
34+ if ($actionType === 'email') {
35+ $account = false;
36+ if ($email) {
37+ $account = db()->fetchOne("
38+ SELECT `a`.`id`, `a`.`name`, `a`.`email`, `za`.`activekey`
39+ FROM `accounts` AS `a`
40+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
41+ WHERE `a`.`email` = ?
42+ ORDER BY `a`.`id` ASC
43+ LIMIT 1;
44+ ", [$email]);
45+ } elseif ($character) {
46+ $account = db()->fetchOne("
47+ SELECT `a`.`id`, `a`.`name`, `a`.`email`, `za`.`activekey`
48+ FROM `players` AS `p`
49+ INNER JOIN `accounts` AS `a` ON `p`.`account_id` = `a`.`id`
50+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
51+ WHERE `p`.`name` = ?
52+ LIMIT 1;
53+ ", [$character]);
54+ }
55+
56+ if (is_array($account) && !empty($account['email'])) {
57+ $recoverylink = $config['site_url'] . '/recovery.php?action=lostaccount_reset&a=' . (int)$account['id'] . '&k=' . (int)$account['activekey'];
58+ $mailer = new Mail($config['mailserver']);
59+ $title = t('recovery.mail_subject_lostaccount', ['host' => $_SERVER['HTTP_HOST']]);
60+ $body = '<h1>' . t('recovery.title') . '</h1>';
61+ $body .= '<p>' . t('recovery.mail_lostaccount_intro') . '</p>';
62+ $body .= '<p><a href="' . htmlspecialchars($recoverylink, ENT_QUOTES, 'UTF-8') . '" target="_BLANK">' . htmlspecialchars($recoverylink, ENT_QUOTES, 'UTF-8') . '</a></p>';
63+ $body .= '<p>' . t('recovery.mail_ignore') . '</p>';
64+ $body .= '<hr><p>' . t('recovery.mail_noreply') . '</p>';
65+ $mailer->sendMail((string)$account['email'], $title, $body, (string)$account['name']);
66+ }
67+
68+ ?>
69+ <h1><?= t('recovery.found') ?></h1>
70+ <p><?= t('recovery.sent_generic') ?></p>
71+ <?php
72+ } else {
73+ ?>
74+ <h1><?= t('recovery.title') ?></h1>
75+ <p><?= t('recovery.not_automated') ?></p>
76+ <?php
77+ }
78+ } elseif (!$username) {
79+ // Recover username
80+ $salt = '';
81+ if (znote_server_adapter()->normalizedEngine() === 'TFS_03' && config('salt') === true) {
82+ $saltdata = db()->fetchOne(
83+ "SELECT `salt` FROM `accounts` WHERE `email` = ? LIMIT 1;",
84+ [$email]
85+ );
86+ if ($saltdata !== false) $salt .= $saltdata['salt'];
87+ }
88+
89+ if (znote_server_adapter()->accountIdentityColumn() !== 'id')
90+ $candidate = db()->fetchOne(
91+ "SELECT `p`.`id` AS `player_id`, `a`.`id` AS `account_id`, `a`.`name`, `a`.`password`
92+ FROM `players` `p`
93+ INNER JOIN `accounts` `a` ON `p`.`account_id` = `a`.`id`
94+ WHERE `p`.`name` = ? AND `a`.`email` = ?
95+ LIMIT 1;",
96+ [$character, $email]
97+ );
98+ else
99+ $candidate = db()->fetchOne(
100+ "SELECT `p`.`id` AS `player_id`, `a`.`id` AS `account_id`, `a`.`id` AS `name`, `a`.`password`
101+ FROM `players` `p`
102+ INNER JOIN `accounts` `a` ON `p`.`account_id` = `a`.`id`
103+ WHERE `p`.`name` = ? AND `a`.`email` = ?
104+ LIMIT 1;",
105+ [$character, $email]
106+ );
107+
108+ $user = false;
109+ if ($candidate !== false && user_verify_login_password((int)$candidate['account_id'], (string)$password, (string)$candidate['password'], $salt)) {
110+ $user = $candidate;
111+ }
112+
113+ if ($user !== false) {
114+ // Found user
115+
116+ $mailer = new Mail($config['mailserver']);
117+ $title = t('recovery.mail_subject_username', ['host' => $_SERVER['HTTP_HOST']]);
118+ $body = '<h1>' . t('recovery.title') . '</h1>';
119+ $body .= '<p>' . t('recovery.your_username2') . ' <b>' . htmlspecialchars((string)$user['name'], ENT_QUOTES, 'UTF-8') . '</b><br>';
120+ $body .= t('recovery.mail_enjoy_stay', ['site' => $config['mailserver']['fromName']]) . ' <br>';
121+ $body .= '<hr>' . t('recovery.mail_noreply') . '</p>';
122+ $mailer->sendMail($email, $title, $body, $user['name']);
123+
124+ ?>
125+ <h1><?= t('recovery.found') ?></h1>
126+ <p><?= t('recovery.sent_username2') ?> <b><?php echo $email; ?></b>.</p>
127+ <p><?= t('recovery.check_junk') ?></p>
128+ <?php
129+ } else {
130+ // Wrong submitted info
131+ ?>
132+ <h1><?= t('recovery.failed') ?></h1>
133+ <p><?= t('recovery.wrong_data') ?></p>
134+ <?php
135+ }
136+
137+ } elseif (!$password) {
138+ // Recover password
139+ $newpass = rand(100000000, 999999999);
140+ $salt = '';
141+ if (znote_server_adapter()->normalizedEngine() !== 'TFS_03') {
142+ // TFS 0.2 and 1.0
143+ $password = sha1($newpass);
144+ } else {
145+ // TFS 0.3/4
146+ if (config('salt') === true) {
147+ $saltdata = db()->fetchOne(
148+ "SELECT `salt` FROM `accounts` WHERE `email` = ? LIMIT 1;",
149+ [$email]
150+ );
151+ if ($saltdata !== false) $salt .= $saltdata['salt'];
152+ }
153+ $password = sha1($salt.$newpass);
154+ }
155+
156+ if (znote_server_adapter()->accountIdentityColumn() !== 'id')
157+ $user = db()->fetchOne(
158+ "SELECT `p`.`id` AS `player_id`, `a`.`name`, `a`.`id` AS `account_id`
159+ FROM `players` `p`
160+ INNER JOIN `accounts` `a` ON `p`.`account_id` = `a`.`id`
161+ WHERE `p`.`name` = ? AND `a`.`email` = ? AND `a`.`name` = ?
162+ LIMIT 1;",
163+ [$character, $email, $username]
164+ );
165+ else
166+ $user = db()->fetchOne(
167+ "SELECT `p`.`id` AS `player_id`, `a`.`id` AS `account_id`, `a`.`id` AS `name`
168+ FROM `players` `p`
169+ INNER JOIN `accounts` `a` ON `p`.`account_id` = `a`.`id`
170+ WHERE `p`.`name` = ? AND `a`.`email` = ? AND `a`.`id` = ?
171+ LIMIT 1;",
172+ [$character, $email, $username]
173+ );
174+
175+ if ($user !== false) {
176+ // Found user
177+ // Give him the new password
178+ db()->execute(
179+ "UPDATE `accounts` SET `password` = ? WHERE `id` = ? LIMIT 1;",
180+ [$password, (int)$user['account_id']]
181+ );
182+ user_set_website_password_hash((int)$user['account_id'], (string)$newpass);
183+ // Send him a mail with the new password
184+ $mailer = new Mail($config['mailserver']);
185+ $title = t('recovery.mail_subject_password', ['host' => $_SERVER['HTTP_HOST']]);
186+ $body = '<h1>' . t('recovery.title') . '</h1>';
187+ $body .= '<p>' . t('recovery.new_password') . ' <b>' . htmlspecialchars((string)$newpass, ENT_QUOTES, 'UTF-8') . '</b><br>';
188+ $body .= t('recovery.recommend_change_password') . ' <br>';
189+ $body .= t('recovery.mail_enjoy_stay', ['site' => $config['mailserver']['fromName']]) . ' <br>';
190+ $body .= '<hr>' . t('recovery.mail_noreply') . '</p>';
191+ $mailer->sendMail($email, $title, $body, $user['name']);
192+ ?>
193+ <h1><?= t('recovery.found') ?></h1>
194+ <p><?= t('recovery.sent_password') ?> <b><?php echo $email; ?></b>.</p>
195+ <p><?= t('recovery.check_junk') ?></p>
196+ <?php
197+ } else {
198+ // Wrong submitted info
199+ ?>
200+ <h1><?= t('recovery.failed') ?></h1>
201+ <p><?= t('recovery.wrong_data') ?></p>
202+ <?php
203+ }
204+ } else { // Token
205+ $candidate = db()->fetchOne(
206+ "SELECT `a`.`id`, `a`.`name`, `a`.`password`, `za`.`activekey`
207+ FROM `accounts` AS `a`
208+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
209+ WHERE `a`.`name` = ? AND `a`.`email` = ?
210+ LIMIT 1;",
211+ [$username, $email]
212+ );
213+ $user = false;
214+ if ($candidate !== false && user_verify_login_password((int)$candidate['id'], (string)$password, (string)$candidate['password'])) {
215+ $user = $candidate;
216+ }
217+ if ($user !== false) {
218+ // Found user
219+ $recoverylink = $config['site_url'] . '/recovery.php?a='.$user['id'].'&k='.$user['activekey'];
220+ $mailer = new Mail($config['mailserver']);
221+ $title = $config['site_title'] . ': ' . t('recovery.remove_2fa') . ' link';
222+ $body = '<h1>' . t('recovery.remove_2fa') . '</h1>';
223+ $body .= '<p>' . t('recovery.remove_2fa_confirm_link') . '<br>';
224+ $body .= '<a href="' . htmlspecialchars($recoverylink, ENT_QUOTES, 'UTF-8') . '" target="_BLANK">' . htmlspecialchars($recoverylink, ENT_QUOTES, 'UTF-8') . '</a><br>';
225+ $body .= t('recovery.mail_enjoy_stay', ['site' => $config['mailserver']['fromName']]) . ' <br>';
226+ $body .= '<hr>' . t('recovery.mail_noreply') . '</p>';
227+ $mailer->sendMail($email, $title, $body, $user['name']);
228+ ?>
229+ <h1><?= t('recovery.confirm_email') ?></h1>
230+ <p><?= t('recovery.sent_link') ?> <b><?php echo $email; ?></b>.</p>
231+ <p><?= t('recovery.click_link') ?> <?= t('common.2fa') ?>.</p>
232+ <p><?= t('recovery.check_junk') ?></p>
233+ <?php
234+ } else {
235+ // Wrong submitted info
236+ ?>
237+ <h1><?= t('recovery.failed') ?></h1>
238+ <p><?= t('recovery.wrong_data') ?></p>
239+ <?php
240+ }
241+
242+
243+ }
244+ } else echo t('recovery.captcha_wrong');
245+ } else {
246+
247+ $recoveryAction = (isset($_GET['action']) && !empty($_GET['action'])) ? getValue($_GET['action'] ?? null) : false;
248+ $a = (isset($_GET['a']) && !empty($_GET['a'])) ? (int)$_GET['a'] : false;
249+ $k = (isset($_GET['k']) && !empty($_GET['k'])) ? (int)$_GET['k'] : false;
250+
251+ // '. t('recovery.remove_2fa'). '
252+ if ($a !== false && $k !== false && $recoveryAction === 'lostaccount_reset') {
253+ $account = db()->fetchOne(
254+ "SELECT `a`.`id`, `a`.`name`, `a`.`email`
255+ FROM `accounts` AS `a`
256+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
257+ WHERE `a`.`id` = ? AND `za`.`activekey` = ?
258+ LIMIT 1;",
259+ [$a, $k]
260+ );
261+ if ($account !== false && !empty($account['email'])) {
262+ $newpass = substr(sha1(random_bytes(32)), 0, 12);
263+ if (znote_server_adapter()->normalizedEngine() === 'TFS_03' && config('salt') === true) {
264+ user_change_password03((int)$account['id'], $newpass);
265+ } else {
266+ user_change_password((int)$account['id'], $newpass);
267+ }
268+ db()->execute("UPDATE `znote_accounts` SET `activekey` = ? WHERE `account_id` = ? LIMIT 1;", [rand(100000000, 999999999), (int)$account['id']]);
269+
270+ $mailer = new Mail($config['mailserver']);
271+ $title = t('recovery.mail_subject_recovered', ['host' => $_SERVER['HTTP_HOST']]);
272+ $body = '<h1>' . t('recovery.title') . '</h1>';
273+ $body .= '<p>' . t('recovery.your_username2') . ' <b>' . htmlspecialchars((string)$account['name'], ENT_QUOTES, 'UTF-8') . '</b><br>';
274+ $body .= t('recovery.new_password') . ' <b>' . htmlspecialchars($newpass, ENT_QUOTES, 'UTF-8') . '</b></p>';
275+ $body .= '<p>' . t('recovery.recommend_change_password_now') . '</p>';
276+ $body .= '<hr><p>' . t('recovery.mail_noreply') . '</p>';
277+ $mailer->sendMail((string)$account['email'], $title, $body, (string)$account['name']);
278+ ?>
279+ <h1><?= t('recovery.found') ?></h1>
280+ <p><?= t('recovery.sent_generic') ?></p>
281+ <?php
282+ } else {
283+ ?>
284+ <h1><?= t('recovery.verify_failed2') ?></h1>
285+ <p><?= t('recovery.cannot_auth') ?></p>
286+ <?php
287+ }
288+ } elseif ($a !== false && $k !== false && !engineIsCanary()) {
289+ $account = db()->fetchOne(
290+ "SELECT `a`.`id`, `a`.`secret`, `za`.`secret`
291+ FROM `accounts` AS `a`
292+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
293+ WHERE `a`.`id` = ? AND `za`.`activekey` = ?
294+ LIMIT 1;",
295+ [$a, $k]
296+ );
297+ if ($account !== false) {
298+ db()->execute("UPDATE `accounts` SET `secret` = NULL WHERE `id` = ? LIMIT 1;", [$a]);
299+ db()->execute("UPDATE `znote_accounts` SET `secret` = NULL WHERE `account_id` = ? LIMIT 1;", [$a]);
300+ ?>
301+ <h1><?= t('recovery.2fa_disabled') ?></h1>
302+ <p><?= t('recovery.2fa_disabled_text') ?></p>
303+ <?php
304+ } else {
305+ ?>
306+ <h1><?= t('recovery.verify_failed2') ?></h1>
307+ <p><?= t('recovery.cannot_auth') ?></p>
308+ <?php
309+ }
310+ } else { // Regular view
311+ ?>
312+ <h2><?= t('recovery.welcome_title') ?></h2>
313+
314+ <p><?= t('recovery.intro_text') ?></p>
315+
316+ <p><?= t('recovery.can_intro') ?></p>
317+
318+ <ul class="CustomBulletPointList">
319+ <li><?= t('recovery.can_new_password') ?></li>
320+ <li><?= t('recovery.can_hacked') ?></li>
321+ <li><?= t('recovery.can_change_email') ?></li>
322+ <li><?= t('recovery.can_new_key') ?></li>
323+ <li><?= t('recovery.can_remove_auth') ?></li>
324+ <li><?= t('recovery.can_disable_email_auth') ?></li>
325+ </ul>
326+
327+ <p><?= t('recovery.first_step') ?></p>
328+
329+ <?php
330+ if (in_array($mode, array('username', 'password', 'token'))) {
331+ ?>
332+ <form action="" method="POST">
333+ <label for="email"><?= t('recovery.email_label') ?></label><input type="text" name="email" placeholder="name@mail.com"><br>
334+ <label for="<?= t('common.character') ?>"><?= t('common.label_character') ?> </label><input type="text" name="character"><br>
335+ <?php
336+
337+ if ($mode === 'password') {
338+ echo '<label for="username">'. t('common.label_username2'). '</label> <input type="text" name="username"><br>';
339+ } elseif ($mode === 'username') {
340+ echo '<label for="password">'. t('common.label_password') .'</label> <input type="password" name="password"><br>';
341+ } elseif ($mode === 'token') {
342+ echo '<label for="username">'. t('common.label_username2') .'</label> <input type="text" name="username"><br>';
343+ echo '<label for="password">'. t('common.label_password') .'</label> <input type="password" name="password"><br>';
344+ }
345+
346+ if ($config['use_captcha']) {
347+ ?>
348+ <div class="g-recaptcha" data-sitekey="<?php echo $config['captcha_site_key']; ?>"></div>
349+ <?php
350+ }
351+ ?>
352+ <input type="submit" value="<?= t('recovery.submit') ?>">
353+ </form>
354+ <?php
355+ } else {
356+ ?>
357+ <?php if (function_exists('tco_panel_open')) { tco_panel_open(t('recovery.panel_title')); } ?>
358+ <form action="" method="post" class="lostaccount-form">
359+ <input type="hidden" name="character" value="">
360+ <div class="lostaccount-field-title"><?= t('recovery.field_character') ?></div>
361+ <input type="text" name="nick" size="40" autofocus>
362+ <div class="lostaccount-field-title"><?= t('recovery.field_email') ?></div>
363+ <input type="text" name="email_rcv" size="40">
364+ <div class="lostaccount-field-title"><?= t('recovery.field_action') ?></div>
365+ <label class="lostaccount-option"><input type="radio" name="action_type" value="email" checked> <?= t('recovery.option_email') ?></label>
366+ <label class="lostaccount-option"><input type="radio" name="action_type" value="reckey"> <?= t('recovery.option_reckey') ?></label>
367+ <label class="lostaccount-option"><input type="radio" name="action_type" value="no_char"> <?= t('recovery.option_no_char') ?></label>
368+ <?php if ($config['use_captcha']) { ?>
369+ <div class="g-recaptcha" data-sitekey="<?php echo $config['captcha_site_key']; ?>"></div>
370+ <?php } ?>
371+ <input type="submit" value="<?= t('recovery.submit') ?>">
372+ </form>
373+ <?php if (function_exists('tco_panel_close')) { tco_panel_close(); } ?>
374+ <?php
375+ }
376+ }
377+ }
378+} else {
379+ ?>
380+ <h1><?= t('recovery.disabled') ?></h1>
381+ <p><?= t('recovery.disabled_text2') ?></p>
382+ <?php
383+}
384+if (function_exists('tco_recovery_close')) {
385+ tco_recovery_close();
386+}
387+theme_close(); ?>
A register.php +158-0 View file
@@ -0,0 +1,158 @@
1+<?php
2+require_once 'engine/init.php';
3+logged_in_redirect();
4+theme_open();
5+require_once('config.countries.php');
6+
7+if (empty($_POST) === false) {
8+ // $_POST['']
9+ $required_fields = array('username', 'password', 'password_again', 'email', 'selected');
10+ foreach($_POST as $key=>$value) {
11+ if (empty($value) && in_array($key, $required_fields) === true) {
12+ $errors[] = t('reg.fill_all');
13+ break 1;
14+ }
15+ }
16+
17+ // check errors (= user exist, pass long enough
18+ if (empty($errors) === true) {
19+ /* Token used for cross site scripting security */
20+ if (!Token::isValid($_POST['token'] ?? null)) {
21+ $errors[] = t('login.token_invalid');
22+ }
23+
24+ if ($config['use_captcha']) {
25+ if(!verifyGoogleReCaptcha($_POST['g-recaptcha-response'])) {
26+ $errors[] = t('reg.captcha');
27+ }
28+ }
29+
30+ if (user_exist($_POST['username']) === true) {
31+ $errors[] = t('reg.name_taken');
32+ }
33+
34+ // Don't allow "default admin names in config.php" access to register.
35+ $isNoob = in_array(strtolower($_POST['username']), $config['page_admin_access']) ? true : false;
36+ if ($isNoob) {
37+ $errors[] = t('reg.name_blocked');
38+ }
39+ if ($config['client'] >= 830) {
40+ if (preg_match("/^[a-zA-Z0-9]+$/", $_POST['username']) == false) {
41+ $errors[] = t('reg.name_chars');
42+ }
43+ } else {
44+ if (preg_match("/^[0-9]+$/", $_POST['username']) == false) {
45+ $errors[] = t('reg.name_digits');
46+ }
47+ if ((int)$_POST['username'] < 100000 || (int)$_POST['username'] > 999999999) {
48+ $errors[] = t('reg.name_length_num');
49+ }
50+ }
51+ // name restriction
52+ $resname = explode(" ", $_POST['username']);
53+ foreach($resname as $res) {
54+ if(in_array(strtolower($res), $config['invalidNameTags'])) {
55+ $errors[] = t('reg.restricted_word');
56+ }
57+ else if(strlen($res) == 1) {
58+ $errors[] = t('reg.words_too_short');
59+ }
60+ }
61+ if (strlen($_POST['username']) > 32) {
62+ $errors[] = t('reg.name_too_long');
63+ }
64+ // end name restriction
65+ if (strlen($_POST['password']) < 6) {
66+ $errors[] = t('reg.pw_too_short');
67+ }
68+ if (strlen($_POST['password']) > 29) {
69+ $errors[] = t('reg.pw_too_long');
70+ }
71+ if ($_POST['password'] !== $_POST['password_again']) {
72+ $errors[] = t('reg.pw_mismatch');
73+ }
74+ if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) === false) {
75+ $errors[] = t('reg.email_invalid');
76+ }
77+ if (user_email_exist($_POST['email']) === true) {
78+ $errors[] = t('reg.email_taken');
79+ }
80+ if ($_POST['selected'] != 1) {
81+ $errors[] = t('reg.accept_rules');
82+ }
83+ if ($config['validate_IP'] === true) {
84+ if (validate_ip(getIP()) === false) {
85+ $errors[] = t('reg.bad_ip');
86+ }
87+ }
88+ if (strlen($_POST['flag']) < 1) {
89+ $errors[] = t('reg.choose_country');
90+ }
91+ }
92+}
93+
94+?>
95+<?php view('register_header'); ?>
96+<?php
97+if (isset($_GET['success']) && empty($_GET['success'])) {
98+ view('register_success', ['emailRequired' => (bool)$config['mailserver']['register']]);
99+} elseif (isset($_GET['authenticate']) && empty($_GET['authenticate'])) {
100+ // Authenticate user, fetch user id and activation key
101+ $auid = (isset($_GET['u']) && (int)$_GET['u'] > 0) ? (int)$_GET['u'] : false;
102+ $akey = (isset($_GET['k']) && (int)$_GET['k'] > 0) ? (int)$_GET['k'] : false;
103+ // Find a match
104+ $user = db()->fetchOne("SELECT `id`, `active`, `active_email` FROM `znote_accounts` WHERE `account_id` = ? AND `activekey` = ? LIMIT 1;", [$auid, $akey]);
105+ if ($user !== false) {
106+ $userId = (int) $user['id'];
107+ $active = (int) $user['active'];
108+ $active_email = (int) $user['active_email'];
109+ // Enable the account to login
110+ if ($active == 0 || $active_email == 0) {
111+ db()->execute("UPDATE `znote_accounts` SET `active` = '1', `active_email` = '1' WHERE `id` = ? LIMIT 1;", [$userId]);
112+ }
113+ view('register_authenticate_result', ['ok' => true]);
114+ } else {
115+ view('register_authenticate_result', ['ok' => false]);
116+ }
117+} else {
118+ if (empty($_POST) === false && empty($errors) === true) {
119+ if ($config['log_ip']) {
120+ znote_visitor_insert_detailed_data(1);
121+ }
122+
123+ //Register
124+ $register_data = array(
125+ 'name' => $_POST['username'],
126+ 'password' => $_POST['password'],
127+ 'email' => $_POST['email'],
128+ 'created' => time(),
129+ 'ip' => getIPLong(),
130+ 'flag' => $_POST['flag']
131+ );
132+
133+ $accountId = user_create_account($register_data, $config['mailserver']);
134+
135+ $createPremiumDays = max(0, min(999, (int)($config['account_create_premdays'] ?? 0)));
136+ if ($accountId > 0 && $createPremiumDays > 0) {
137+ user_account_add_premdays($accountId, $createPremiumDays);
138+ }
139+
140+ // Plugins can react to a new account: a welcome bonus, a webhook, an
141+ // entry in a referral ledger.
142+ znote_hook('account.registered', array(
143+ 'name' => $register_data['name'] ?? '',
144+ 'email' => $register_data['email'] ?? '',
145+ ));
146+ if (!$config['mailserver']['debug']) header('Location: register.php?success');
147+ exit();
148+ //End register
149+
150+ } else if (empty($errors) === false){
151+ echo '<font color="red"><b>';
152+ echo output_errors($errors);
153+ echo '</b></font>';
154+ }
155+ view('register_form');
156+}
157+theme_close();
158+?>
A serverinfo.php +50-0 View file
@@ -0,0 +1,50 @@
1+<?php require_once 'engine/init.php'; theme_open();
2+// Calculate integer values into days, hours, minutes, seconds
3+function toDuration($ms) {
4+ $duration['day'] = $ms / (24 * 60 * 60 * 1000);
5+ if (($duration['day'] - (int)$duration['day']) > 0)
6+ $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24;
7+ if (isset($duration['hour'])) {
8+ if (($duration['hour'] - (int)$duration['hour']) > 0)
9+ $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60;
10+ if (isset($duration['minute'])) {
11+ if (($duration['minute'] - (int)$duration['minute']) > 0)
12+ $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60;
13+ }
14+ }
15+ $tmp = array();
16+ foreach ($duration as $type => $value) {
17+ if ($value >= 1) {
18+ $pluralType = ((int)$value === 1) ? $type : $type . 's';
19+ if ($type !== 'second') $tmp[] = (int)$value . " $pluralType";
20+ else $tmp[] = $value . " $pluralType";
21+ }
22+ }
23+ return implode(', ', $tmp);
24+}
25+function toYesNo($bool) {
26+ return ($bool) ? 'Yes' : 'No';
27+}
28+$serverAdmin = (user_logged_in() && is_admin($user_data));
29+$showStagesForm = false;
30+$showConfigForm = false;
31+$stagesUpdated = false;
32+$stagesFailed = false;
33+
34+$stagesData = serverdata_load('stages');
35+if ($stagesData === false && is_file(serverdata_file('stages.xml'))) {
36+ serverdata_rebuild('stages');
37+ $stagesData = serverdata_load('stages');
38+}
39+
40+$luaConfig = serverdata_load('config');
41+
42+$stages = false;
43+
44+view('serverinfo');
45+
46+if (!minimap_was_rendered()) {
47+ minimap_render();
48+}
49+
50+theme_close();
A settings.php +67-0 View file
@@ -0,0 +1,67 @@
1+<?php
2+require_once 'engine/init.php';
3+protect_page();
4+theme_open();
5+require_once('config.countries.php');
6+
7+if (empty($_POST) === false) {
8+ // $_POST['']
9+ /* Token used for cross site scripting security */
10+ if (!Token::isValid($_POST['token'])) {
11+ $errors[] = t('login.token_invalid');
12+ }
13+ $required_fields = array('new_email', 'new_flag');
14+ foreach($_POST as $key=>$value) {
15+ if (empty($value) && in_array($key, $required_fields) === true) {
16+ $errors[] = t('reg.fill_all');
17+ break 1;
18+ }
19+ }
20+
21+ if (empty($errors) === true) {
22+ if (filter_var($_POST['new_email'], FILTER_VALIDATE_EMAIL) === false) {
23+ $errors[] = t('reg.email_invalid');
24+ } else if (user_email_exist($_POST['new_email']) === true && $user_data['email'] !== $_POST['new_email']) {
25+ $errors[] = t('reg.email_taken');
26+ }
27+ }
28+}
29+
30+/**
31+ * What the view has to render: 'success', 'errors' or 'form'.
32+ * The account write stays here - a theme must never carry it.
33+ */
34+$formState = 'form';
35+
36+if (isset($_GET['success']) === true && empty($_GET['success']) === true) {
37+ $formState = 'success';
38+
39+} elseif (empty($_POST) === false && empty($errors) === true) {
40+
41+ $update_data = array(
42+ 'email' => $_POST['new_email']
43+ );
44+
45+ $update_znote_data = array(
46+ 'flag' => getValue($_POST['new_flag'] ?? null),
47+ 'active_email' => '0'
48+ );
49+
50+ // If the address was previously verified, take back the bonus points.
51+ if ($user_znote_data['active_email'] > 0) {
52+ $update_znote_data['points'] = $user_znote_data['points'] - $config['mailserver']['verify_email_points'];
53+ }
54+
55+ user_update_account($update_data);
56+ user_update_znote_account($update_znote_data);
57+
58+ header('Location: settings.php?success');
59+ exit;
60+
61+} elseif (empty($errors) === false) {
62+ $formState = 'errors';
63+}
64+
65+view('settings');
66+
67+theme_close();
A shop.php +188-0 View file
@@ -0,0 +1,188 @@
1+<?php require_once 'engine/init.php';
2+znote_csrf_protect_public_post();
3+theme_open();
4+
5+if (isset($_GET['callback']) && $_GET['callback'] === 'processing') {
6+ echo '<script>alert(' . json_encode(t('shop.payment_processing')) . ');</script>';
7+}
8+
9+// Import from config:
10+$shop = $config['shop'];
11+if ($shop['loginToView'] === true) protect_page();
12+$loggedin = user_logged_in();
13+
14+function shop_db_offer_columns(): array {
15+ $columns = array();
16+ $rows = db()->fetchAll("SHOW COLUMNS FROM `znote_shop_offers`;");
17+
18+ if (is_array($rows)) {
19+ foreach ($rows as $row) {
20+ if (!empty($row['Field'])) {
21+ $columns[(string)$row['Field']] = true;
22+ }
23+ }
24+ }
25+
26+ return $columns;
27+}
28+
29+function shop_load_db_offers(): array {
30+ $columns = shop_db_offer_columns();
31+ $where = !empty($columns['active']) ? "WHERE `active` = 1" : "";
32+ $order = !empty($columns['sort_order'])
33+ ? "ORDER BY `sort_order` ASC, `id` ASC"
34+ : "ORDER BY `id` ASC";
35+
36+ $rows = db()->fetchAll("
37+ SELECT `id`, `type`, `itemid`, `count`, `description`, `points`
38+ FROM `znote_shop_offers`
39+ {$where}
40+ {$order};
41+ ");
42+
43+ if (!is_array($rows)) {
44+ return array();
45+ }
46+
47+ $offers = array();
48+ foreach ($rows as $row) {
49+ $itemid = (int)$row['itemid'];
50+ if ((int)$row['type'] === 5 && $itemid > 0) {
51+ $male = (int)floor($itemid / 10000);
52+ $female = (int)($itemid % 10000);
53+ $itemid = array($male, $female);
54+ }
55+
56+ $offers[(int)$row['id']] = array(
57+ 'type' => (int)$row['type'],
58+ 'itemid' => $itemid,
59+ 'count' => (int)$row['count'],
60+ 'description' => (string)$row['description'],
61+ 'points' => (int)$row['points'],
62+ );
63+ }
64+
65+ return $offers;
66+}
67+
68+$shop_list = shop_load_db_offers();
69+
70+if ($loggedin === true) {
71+ $postedShopSession = (string)($_POST['session'] ?? '');
72+ $storedShopSession = (string)($_SESSION['shop_session'] ?? '');
73+ if (!empty($_POST['buy']) && $postedShopSession !== '' && $storedShopSession !== '' && hash_equals($storedShopSession, $postedShopSession)) {
74+ unset($_SESSION['shop_session']);
75+ $time = time();
76+ $cid = (int)$user_data['id'];
77+ // Sanitizing post, setting default buy value
78+ $buy = false;
79+ $post = (int)$_POST['buy'];
80+
81+ foreach ($shop_list as $key => $value) {
82+ if ($key === $post) {
83+ $buy = $value;
84+ }
85+ }
86+ if ($buy === false) die("Error: Shop offer ID mismatch.");
87+
88+ // Plugins may adjust what this offer costs - a discount code, a happy
89+ // hour, a loyalty rebate. The filtered value is what gets checked,
90+ // charged and logged, so the three can never disagree.
91+ $buy['points'] = max(0, (int)znote_hook_filter('shop.price', (int)$buy['points'], array(
92+ 'account_id' => $cid,
93+ 'offer_id' => $post,
94+ 'offer' => $buy,
95+ )));
96+
97+ // If this is an outfit offer, convert array into an integer.
98+ if ($buy['type'] == 5) {
99+ if (is_array($buy['itemid'])) {
100+ if (COUNT($buy['itemid']) == 2) $buy['itemid'] = ($buy['itemid'][0] * 1000) + $buy['itemid'][1];
101+ else $buy['itemid'] = $buy['itemid'][0];
102+ }
103+ }
104+
105+ $db = db();
106+ if (!$db->beginTransaction()) {
107+ die("Failed to start shop transaction.");
108+ }
109+
110+ $data = $db->fetchOne("SELECT `points` FROM `znote_accounts` WHERE `account_id` = ? LIMIT 1 FOR UPDATE;", [$cid]);
111+ if (!$data) {
112+ $db->rollback();
113+ die("0: Account is not converted to work with Znote AAC");
114+ }
115+
116+ $old_points = (int)$data['points'];
117+ if ($old_points < $buy['points']) {
118+ $db->rollback();
119+ echo '<font color="red" size="4">You need more points, this offer cost '.$buy['points'].' points.</font>';
120+ } else {
121+ $expense_points = (int)$buy['points'];
122+ $orderReady = true;
123+
124+ if (!$db->execute(
125+ "UPDATE `znote_accounts` SET `points` = `points` - ? WHERE `account_id` = ? AND `points` >= ?;",
126+ [$expense_points, $cid, $expense_points]
127+ )) {
128+ $orderReady = false;
129+ }
130+
131+ // Do the magic (insert into db, or change sex etc)
132+ // If type is 2 or 3
133+ if ($orderReady && $buy['type'] == 2) {
134+ // Add premium days to account
135+ $orderReady = user_account_add_premdays($cid, $buy['count']);
136+ $successMessage = '<font color="green" size="4">You now have '.$buy['count'].' additional days of premium membership.</font>';
137+ } else if ($orderReady) {
138+ $orderReady = $db->execute(
139+ "INSERT INTO `znote_shop_orders` (`account_id`, `type`, `itemid`, `count`, `time`) VALUES (?, ?, ?, ?, ?);",
140+ [$cid, (int)$buy['type'], (int)$buy['itemid'], (int)$buy['count'], $time]
141+ );
142+
143+ if ($buy['type'] == 3) {
144+ $successMessage = '<font color="green" size="4">'. t('shop.gender_unlocked') .'</font>';
145+ } else if ($buy['type'] == 4) {
146+ $successMessage = '<font color="green" size="4">'. t('shop.name_unlocked') .'</font>';
147+ } else {
148+ $successMessage = '<font color="green" size="4">Your order is ready to be delivered. Write this command in-game to get it: [!shop].<br>Make sure you are in depot and can carry it before executing the command!</font>';
149+ }
150+ }
151+
152+ if ($orderReady) {
153+ $orderReady = $db->execute(
154+ "INSERT INTO `znote_shop_logs` (`account_id`, `player_id`, `type`, `itemid`, `count`, `points`, `time`) VALUES (?, 0, ?, ?, ?, ?, ?);",
155+ [$cid, (int)$buy['type'], (int)$buy['itemid'], (int)$buy['count'], (int)$buy['points'], $time]
156+ );
157+ }
158+
159+ if ($orderReady) {
160+ $db->commit();
161+ $user_znote_data['points'] = $old_points - $expense_points;
162+ echo $successMessage;
163+
164+ // Plugins can react to a purchase - a coupon ledger, a Discord message,
165+ // a loyalty counter. They cannot change what was bought; this is a
166+ // notification, fired after the points have already been taken.
167+ znote_hook('shop.purchased', array(
168+ 'account_id' => $cid,
169+ 'offer_id' => $post,
170+ 'type' => $buy['type'],
171+ 'itemid' => $buy['itemid'],
172+ 'count' => $buy['count'],
173+ 'points' => $buy['points'],
174+ ));
175+ $buy['points'] = 0;
176+ } else {
177+ $db->rollback();
178+ echo '<font color="red" size="4">Shop purchase failed. Please try again or contact staff.</font>';
179+ }
180+ }
181+ //var_dump($buy);
182+ //echo '<font color="red" size="4">'. $_POST['buy'] .'</font>';
183+ }
184+}
185+
186+view('shop');
187+
188+theme_close();
A special/.htaccess +3-0 View file
@@ -0,0 +1,3 @@
1+order deny,allow
2+deny from all
3+allow from 127.0.0.1 localhost ::1
A special/convertoldshoppoints.php +32-0 View file
@@ -0,0 +1,32 @@
1+<?php
2+require '../config.php';
3+require '../engine/database/connect.php';
4+?>
5+
6+<h1>Gesior and Modern shop points to Znote AAC shop points</h1>
7+<p>Convert donation/shop points from previous Gesior/Modern installation to Znote AAC:</p>
8+<?php
9+ $accounts = db()->fetchAll("SELECT `id`, `premium_points` FROM `accounts` WHERE `premium_points` > 0;");
10+ $accountids = array();
11+ foreach ($accounts as $acc) $accountids[] = $acc['id'];
12+
13+ if ($accounts !== false) echo "<p>Detected: ". count($accounts) ." accounts who have points in old system.</p>";
14+ else die("<h1>All accounts already converted. :)</h1>");
15+
16+ $placeholders = implode(',', array_fill(0, count($accountids), '?'));
17+ $znote_accounts = db()->fetchAll("SELECT `account_id`, `points` FROM `znote_accounts` WHERE `account_id` IN ($placeholders);", $accountids);
18+
19+ if (count($accounts) !== count($znote_accounts)) die("<h1><font color='red'>Failed to syncronize accounts. You need to convert all accounts to Znote AAC first!</font></h1>");
20+
21+ // Order old accounts by id.
22+ $idaccounts = array();
23+ foreach ($accounts as $acc) {
24+ $idaccounts[$acc['id']] = $acc['premium_points'];
25+ }
26+ foreach ($znote_accounts as $acc) {
27+ db()->execute("UPDATE `znote_accounts` SET `points` = ? WHERE `account_id` = ? LIMIT 1;", [$acc['points'] + $idaccounts[$acc['account_id']], $acc['account_id']]);
28+ }
29+ db()->execute("UPDATE `accounts` SET `premium_points` = 0;");
30+
31+ echo "<h1><font color='green'>Successfully converted all points!</font></h1>";
32+?>
A special/database2znoteaac.php +153-0 View file
@@ -0,0 +1,153 @@
1+<?php
2+require '../config.php';
3+require '../engine/database/connect.php';
4+require '../engine/function/general.php';
5+require '../engine/function/users.php';
6+?>
7+
8+<h1>Old database to Znote AAC compatibility converter:</h1>
9+<p>Converting accounts and characters to work with Znote AAC:</p>
10+<?php
11+ // some variables
12+ $updated_acc = 0;
13+ // $updated_acc += 1;
14+ $updated_char = 0;
15+ // $updated_char += 1;
16+ $updated_pass = 0;
17+
18+ // install functions
19+ function fetch_all_accounts() {
20+ $results = db()->fetchAll("SELECT `id` FROM `accounts`");
21+ $accounts = array();
22+ foreach ($results as $row) {
23+ $accounts[] = $row['id'];
24+ }
25+ return (count($accounts) > 0) ? $accounts : false;
26+ }
27+
28+ function user_count_znote_accounts() {
29+ $data = db()->fetchOne("SELECT COUNT(`account_id`) AS `count` from `znote_accounts`;");
30+ return ($data !== false) ? $data['count'] : 0;
31+ }
32+
33+ function user_character_is_compatible($pid) {
34+ $data = db()->fetchOne("SELECT COUNT(`player_id`) AS `count` from `znote_players` WHERE `player_id` = ?;", [$pid]);
35+ return ($data !== false) ? $data['count'] : 0;
36+ }
37+
38+ function fetch_znote_accounts() {
39+ $results = db()->fetchAll("SELECT `account_id` FROM `znote_accounts`");
40+ $accounts = array();
41+ foreach ($results as $row) {
42+ $accounts[] = $row['account_id'];
43+ }
44+ return (count($accounts) > 0) ? $accounts : false;
45+ }
46+ // end install functions
47+
48+ // count all accounts, znote accounts, find out which accounts needs to be converted.
49+ $all_account = fetch_all_accounts();
50+ $znote_account = fetch_znote_accounts();
51+ if ($all_account !== false) {
52+ if ($znote_account !== false) { // If existing znote compatible account exists:
53+ foreach ($all_account as $all) { // Loop through every element in znote_account array
54+ if (!in_array($all, $znote_account)) {
55+ $old_accounts[] = $all;
56+ }
57+ }
58+ } else {
59+ foreach ($all_account as $all) {
60+ $old_accounts[] = $all;
61+ }
62+ }
63+ }
64+ // end ^
65+
66+ // Send count status
67+ if (isset($all_account) && $all_account !== false) {
68+ echo '<br>';
69+ echo 'Total accounts detected: '. count($all_account) .'.';
70+
71+ if (isset($znote_account) && $znote_account !== false) {
72+ echo '<br>';
73+ echo 'Znote compatible accounts detected: '. count($znote_account) .'.';
74+
75+ if (isset($old_accounts)) {
76+ echo '<br>';
77+ echo 'Old accounts detected: '. count($old_accounts) .'.';
78+ }
79+ } else {
80+ echo '<br>';
81+ echo 'Znote compatible accounts detected: 0.';
82+ }
83+ echo '<br>';
84+ echo '<br>';
85+ } else {
86+ echo '<br>';
87+ echo 'Total accounts detected: 0.';
88+ }
89+ // end count status
90+
91+ // validate accounts
92+ if (isset($old_accounts) && $old_accounts !== false) {
93+ $time = time();
94+ foreach ($old_accounts as $old) {
95+
96+ // Make acc data compatible:
97+ db()->execute("INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `flag`) VALUES (?, 0, ?, '')", [$old, $time]);
98+ $updated_acc += 1;
99+
100+ // Fetch unsalted password
101+ if ($config['ServerEngine'] == 'TFS_03' && $config['salt'] === true) {
102+ $password = user_data($old, 'password', 'salt');
103+ $p_pass = str_replace($password['salt'],"",$password['password']);
104+ }
105+ if ($config['ServerEngine'] == 'TFS_02' || $config['salt'] === false) {
106+ $password = user_data($old, 'password');
107+ $p_pass = $password['password'];
108+ }
109+
110+ // Verify lenght of password is less than 28 characters (most likely a plain password)
111+ if (strlen($p_pass) < 28 && $old > 1) {
112+ // encrypt it with sha1
113+ if ($config['ServerEngine'] == 'TFS_02' || $config['salt'] === false) $p_pass = sha1($p_pass);
114+ if ($config['ServerEngine'] == 'TFS_03' && $config['salt'] === true) $p_pass = sha1($password['salt'].$p_pass);
115+
116+ // Update their password so they are sha1 encrypted
117+ db()->execute("UPDATE `accounts` SET `password` = ? WHERE `id` = ?;", [$p_pass, $old]);
118+ $updated_pass += 1;
119+ }
120+
121+ }
122+ }
123+
124+ // validate players
125+ if ($all_account !== false) {
126+ $time = time();
127+ foreach ($all_account as $all) {
128+
129+ $chars = user_character_list_player_id($all);
130+ if ($chars !== false) {
131+ // since char list is not false, we found a character list
132+
133+ // Lets loop through the character list
134+ foreach ($chars as $c) {
135+ // Is character not compatible yet?
136+ if (user_character_is_compatible($c['id']) == 0) {
137+ // Then lets make it compatible:
138+ $cid = $c['id'];
139+ db()->execute("INSERT INTO `znote_players` (`player_id`, `created`, `hide_char`, `comment`) VALUES (?, ?, 0, '')", [$cid, $time]);
140+ $updated_char += 1;
141+
142+ }
143+ }
144+ }
145+ }
146+ }
147+
148+ echo "<br><b><font color=\"green\">SUCCESS</font></b><br><br>";
149+ echo 'Updated accounts: '. $updated_acc .'<br>';
150+ echo 'Updated characters: : '. $updated_char .'<br>';
151+ echo 'Detected:'. $updated_pass .' accounts with plain passwords. These passwords has been given sha1 encryption.<br>';
152+ echo '<br>All accounts and characters are compatible with Znote AAC<br>';
153+?>
A special/milestone.txt +5-0 View file
@@ -0,0 +1,5 @@
1+Milestone - What I wish to add to Znote AAC in the future. (Znote AAC TODO/wish list).
2+- Character auction page for donation points.
3+- Semi-live communication with OT.
4+- Live ban, kick, broadcast message, open/close server and Custom commands.
5+- Sub-page system.
A special/repairSkills.php +70-0 View file
@@ -0,0 +1,70 @@
1+<?php require_once 'engine/init.php';
2+
3+/* PLAYER SKILLS REPAIR SCRIPT IF YOU SOMEHOW DELETE PLAYER SKILLS
4+ ---------------------------------------------------------------
5+ Place in root web directory, login to admin account,
6+ and enter site.com/repairSkills.php (with big S).
7+*/
8+
9+protect_page();
10+admin_only($user_data);
11+znote_csrf_protect_public_post();
12+
13+if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
14+ ?>
15+ <h1>Repair missing player skills</h1>
16+ <p>This operation scans every player and inserts default rows where skills are missing.</p>
17+ <form method="post" onsubmit="return confirm('Repair missing skills for every affected player?');">
18+ <button type="submit">Run repair</button>
19+ </form>
20+ <?php
21+ exit;
22+}
23+
24+$Splayers = 0;
25+$Salready = 0;
26+$Sfixed = 0;
27+
28+$players = db()->fetchAll("SELECT `id` FROM `players`;");
29+if ($players !== false) {
30+ $Splayers = count($players);
31+ foreach ($players as $char) {
32+
33+ // Check if player have skills
34+ $skills = db()->fetchOne("SELECT `value` FROM `player_skills` WHERE `player_id` = ? AND `skillid` = 2 LIMIT 1;", [$char['id']]);
35+
36+ // If he dont have any skills
37+ if ($skills === false) {
38+ $Sfixed++;
39+
40+ // Loop through every skill id and give him default skills.
41+ $rows = array();
42+ $params = array();
43+ for ($i = 0; $i < 7; $i++) {
44+ $rows[] = '(?, ?, 10, 0)';
45+ $params[] = $char['id'];
46+ $params[] = $i;
47+ }
48+
49+ db()->execute(
50+ "INSERT INTO `player_skills` (`player_id`, `skillid`, `value`, `count`) VALUES " . implode(', ', $rows) . ";",
51+ $params
52+ );
53+ } else $Salready++;
54+ }
55+ acp_log('maintenance.repair_skills', 'ALL', ['players' => $Splayers, 'repaired' => $Sfixed]);
56+ ?>
57+ <h1>Script run status:</h1>
58+ <p>Players detected: <?php echo $Splayers; ?></p>
59+ <p>Players already fixed: <?php echo $Salready; ?></p>
60+ <p><b>Repaired player accounts: <?php echo $Sfixed; ?></b></p>
61+ <?php
62+} else {
63+ ?>
64+ <h1>No players detected.</h1>
65+ <p>Something went wrong.</p>
66+ <?php
67+}
68+?>
69+
70+<h1>Script run completed.</h1>
A spells.php +25-0 View file
@@ -0,0 +1,25 @@
1+<?php require_once 'engine/init.php'; theme_open();
2+
3+/**
4+ * Spell list.
5+ *
6+ * spells.xml is uploaded and parsed in the admin panel, under Server Info. This
7+ * page only reads the result:
8+ * $spells the spell list, or false when nothing was published
9+ * $showSpellsForm, $spellsUpdated, $spellsFailed kept at false for older themes
10+ */
11+
12+$showSpellsForm = false;
13+$spellsUpdated = false;
14+$spellsFailed = false;
15+
16+$spells = serverdata_load('spells');
17+
18+if ($spells === false && is_file(serverdata_file('spells.xml'))) {
19+ serverdata_rebuild('spells');
20+ $spells = serverdata_load('spells');
21+}
22+
23+view('spells');
24+
25+theme_close();
A SQL/migrations/2.0.0_admin_log.sql +28-0 View file
@@ -0,0 +1,28 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.0 - admin action log
3+--
4+-- Only needed for databases created BEFORE this feature. A fresh import of
5+-- SQL/znote_schema.sql already contains the table.
6+--
7+-- Records mutating actions taken from the admin panel (bans, skill edits,
8+-- points, settings changes, plugin lifecycle, etc). Written by acp_log() in
9+-- engine/function/adminlog.php, read from Admin Panel > Admin Log.
10+--
11+-- Run once:
12+-- mysql -u <user> -p <database> < SQL/migrations/2.0.0_admin_log.sql
13+-- ---------------------------------------------------------------------------
14+
15+CREATE TABLE IF NOT EXISTS `znote_admin_log` (
16+ `id` int NOT NULL AUTO_INCREMENT,
17+ `admin_id` int NOT NULL DEFAULT '0',
18+ `admin_name` varchar(50) NOT NULL DEFAULT '',
19+ `action` varchar(64) NOT NULL,
20+ `target` varchar(191) NOT NULL DEFAULT '',
21+ `details` text NOT NULL,
22+ `ip` varchar(45) NOT NULL DEFAULT '',
23+ `created` int NOT NULL,
24+ PRIMARY KEY (`id`),
25+ KEY `admin_created` (`admin_id`, `created`),
26+ KEY `action_created` (`action`, `created`),
27+ KEY `created` (`created`)
28+) ENGINE=InnoDB;
A SQL/migrations/2.0.0_forum_dedupe.sql +24-0 View file
@@ -0,0 +1,24 @@
1+-- Merge duplicate forum boards.
2+--
3+-- Older schema seeds (and the OT -> ZnoteX converter) could INSERT the default
4+-- boards more than once, leaving two "Discussion", two "Staff Board", etc. This
5+-- keeps the lowest id for each (name, guild_id), moves every thread onto it and
6+-- deletes the emptied duplicates. Safe to run more than once.
7+
8+UPDATE `znote_forum_threads` `t`
9+JOIN `znote_forum` `dup` ON `dup`.`id` = `t`.`forum_id`
10+JOIN (
11+ SELECT `name`, `guild_id`, MIN(`id`) AS `keep_id`
12+ FROM `znote_forum`
13+ GROUP BY `name`, `guild_id`
14+) `k` ON `k`.`name` = `dup`.`name` AND `k`.`guild_id` = `dup`.`guild_id`
15+SET `t`.`forum_id` = `k`.`keep_id`
16+WHERE `t`.`forum_id` <> `k`.`keep_id`;
17+
18+DELETE `f` FROM `znote_forum` `f`
19+JOIN (
20+ SELECT `name`, `guild_id`, MIN(`id`) AS `keep_id`
21+ FROM `znote_forum`
22+ GROUP BY `name`, `guild_id`
23+) `k` ON `k`.`name` = `f`.`name` AND `k`.`guild_id` = `f`.`guild_id`
24+WHERE `f`.`id` <> `k`.`keep_id`;
A SQL/migrations/2.0.0_gallery_image_url.sql +4-0 View file
@@ -0,0 +1,4 @@
1+-- Allows imported MyAAC/Gesior gallery URLs to fit without truncation.
2+
3+ALTER TABLE `znote_images`
4+ MODIFY `image` varchar(255) NOT NULL;
A SQL/migrations/2.0.0_menus.sql +96-0 View file
@@ -0,0 +1,96 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.0 - navigation menus
3+--
4+-- Only needed for databases created BEFORE this feature. A fresh import of
5+-- SQL/znote_schema.sql already contains the table and its default entries.
6+--
7+-- Menu links used to be hardcoded in every theme, which meant editing PHP to
8+-- add a link and duplicating the whole menu in each theme. They live here now
9+-- and are edited from Admin Panel > Menus.
10+--
11+-- Run once:
12+-- mysql -u <user> -p <database> < SQL/migrations/2.0.0_menus.sql
13+-- ---------------------------------------------------------------------------
14+
15+-- Navigation entries, managed from Admin Panel > Menus.
16+-- A theme declares the locations it renders (see layouts/README.md); entries
17+-- are grouped by that location and ordered by sort_order.
18+CREATE TABLE IF NOT EXISTS `znote_menu` (
19+ `id` int NOT NULL AUTO_INCREMENT,
20+ `location` varchar(32) NOT NULL COMMENT 'Theme-declared slot: main, sidebar, footer...',
21+ `parent_id` int NOT NULL DEFAULT '0' COMMENT '0 = top level, else the id of the parent entry',
22+ `label` varchar(64) NOT NULL,
23+ `url` varchar(255) NOT NULL,
24+ `icon` varchar(48) NOT NULL DEFAULT '' COMMENT 'Optional Font Awesome class',
25+ `target` varchar(10) NOT NULL DEFAULT '',
26+ `visibility` varchar(10) NOT NULL DEFAULT 'all' COMMENT 'all, guest, user or admin',
27+ `sort_order` int NOT NULL DEFAULT '0',
28+ `active` tinyint NOT NULL DEFAULT '1',
29+ PRIMARY KEY (`id`),
30+ KEY `loc_sort` (`location`, `active`, `sort_order`, `id`)
31+) ENGINE=InnoDB;
32+
33+INSERT INTO `znote_menu` (`location`, `parent_id`, `label`, `url`, `icon`, `visibility`, `sort_order`)
34+SELECT `seed`.`location`, `seed`.`parent_id`, `seed`.`label`, `seed`.`url`, `seed`.`icon`, `seed`.`visibility`, `seed`.`sort_order`
35+FROM (
36+ SELECT 'main' AS `location`, 0 AS `parent_id`, 'Home' AS `label`, 'index.php' AS `url`, 'fa-home' AS `icon`, 'all' AS `visibility`, 10 AS `sort_order`
37+ UNION ALL SELECT 'main', 0, 'Changelog', 'changelog.php', '', 'all', 20
38+ UNION ALL SELECT 'main', 0, 'Account', 'myaccount.php', 'fa-user-circle', 'user', 30
39+ UNION ALL SELECT 'main', 0, 'Login', 'login.php', 'fa-user-circle', 'guest', 30
40+ UNION ALL SELECT 'main', 0, 'Register', 'register.php', 'fa-key', 'guest', 40
41+ UNION ALL SELECT 'main', 0, 'Downloads', 'downloads.php', '', 'all', 50
42+ UNION ALL SELECT 'main', 0, 'Community', 'onlinelist.php', 'fa-users', 'all', 60
43+ UNION ALL SELECT 'main', 0, 'Highscores', 'highscores.php', '', 'all', 70
44+ UNION ALL SELECT 'main', 0, 'Guilds', 'guilds.php', '', 'all', 80
45+ UNION ALL SELECT 'main', 0, 'Forum', 'forum.php', '', 'all', 90
46+ UNION ALL SELECT 'main', 0, 'Houses', 'houses.php', '', 'all', 100
47+ UNION ALL SELECT 'main', 0, 'Latest deaths', 'deaths.php', '', 'all', 110
48+ UNION ALL SELECT 'main', 0, 'Kill statistics', 'killers.php', '', 'all', 120
49+ UNION ALL SELECT 'main', 0, 'Bans', 'bans.php', '', 'all', 125
50+ UNION ALL SELECT 'main', 0, 'Creatures', 'creatures.php', '', 'all', 135
51+ UNION ALL SELECT 'main', 0, 'Library', 'serverinfo.php', 'fa-book', 'all', 130
52+ UNION ALL SELECT 'main', 0, 'Spells', 'spells.php', '', 'all', 140
53+ UNION ALL SELECT 'main', 0, 'Support', 'support.php', 'fa-info-circle', 'all', 150
54+ UNION ALL SELECT 'main', 0, 'Helpdesk', 'helpdesk.php', '', 'all', 160
55+ UNION ALL SELECT 'main', 0, 'Shop', 'shop.php', 'fa-shopping-cart', 'all', 170
56+ UNION ALL SELECT 'main', 0, 'Buy points', 'buypoints.php', '', 'all', 180
57+ UNION ALL SELECT 'main', 0, 'Admin Panel', 'admin/index.php', 'fa-sliders', 'admin', 190
58+) `seed`
59+LEFT JOIN `znote_menu` `existing`
60+ ON `existing`.`location` = `seed`.`location`
61+ AND `existing`.`parent_id` = 0
62+ AND `existing`.`label` = `seed`.`label`
63+WHERE `existing`.`id` IS NULL;
64+
65+-- Nest the sub-entries under their section. Done as a second pass because the
66+-- parent ids are only known once the rows above exist.
67+UPDATE `znote_menu` `c`
68+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Home'
69+ SET `c`.`parent_id` = `p`.`id`
70+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Changelog');
71+
72+UPDATE `znote_menu` `c`
73+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Account'
74+ SET `c`.`parent_id` = `p`.`id`
75+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Downloads');
76+
77+UPDATE `znote_menu` `c`
78+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Community'
79+ SET `c`.`parent_id` = `p`.`id`
80+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Highscores','Guilds','Forum','Houses','Latest deaths','Kill statistics','Bans');
81+
82+UPDATE `znote_menu` `c`
83+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Library'
84+ SET `c`.`parent_id` = `p`.`id`
85+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Spells','Creatures');
86+
87+UPDATE `znote_menu` `c`
88+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Support'
89+ SET `c`.`parent_id` = `p`.`id`
90+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Helpdesk');
91+
92+UPDATE `znote_menu` `c`
93+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Shop'
94+ SET `c`.`parent_id` = `p`.`id`
95+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Buy points');
96+
A SQL/migrations/2.0.0_pages_and_convert_map.sql +66-0 View file
@@ -0,0 +1,66 @@
1+-- Adds database-backed pages and legacy conversion mapping tables.
2+-- Safe to run more than once.
3+
4+CREATE TABLE IF NOT EXISTS `znote_pages` (
5+ `id` int NOT NULL AUTO_INCREMENT,
6+ `slug` varchar(64) NOT NULL,
7+ `title` varchar(100) NOT NULL,
8+ `body` mediumtext NOT NULL,
9+ `created` int NOT NULL DEFAULT '0',
10+ `updated` int NOT NULL DEFAULT '0',
11+ `player_id` int NOT NULL DEFAULT '0',
12+ `access` tinyint NOT NULL DEFAULT '0',
13+ `active` tinyint NOT NULL DEFAULT '1',
14+ PRIMARY KEY (`id`),
15+ UNIQUE KEY `slug` (`slug`)
16+) ENGINE=InnoDB;
17+
18+CREATE TABLE IF NOT EXISTS `znote_convert_map` (
19+ `id` int NOT NULL AUTO_INCREMENT,
20+ `source` varchar(32) NOT NULL,
21+ `source_table` varchar(64) NOT NULL,
22+ `source_id` varchar(64) NOT NULL,
23+ `target_table` varchar(64) NOT NULL,
24+ `target_id` int NOT NULL,
25+ `created` int NOT NULL,
26+ PRIMARY KEY (`id`),
27+ UNIQUE KEY `source_row` (`source`, `source_table`, `source_id`, `target_table`)
28+) ENGINE=InnoDB;
29+
30+CREATE TABLE IF NOT EXISTS `znote_legacy_tables` (
31+ `id` int NOT NULL AUTO_INCREMENT,
32+ `source` varchar(32) NOT NULL,
33+ `table_name` varchar(64) NOT NULL,
34+ `schema_sql` longtext NOT NULL,
35+ `row_count` int NOT NULL DEFAULT '0',
36+ `captured` int NOT NULL,
37+ PRIMARY KEY (`id`),
38+ UNIQUE KEY `source_table` (`source`, `table_name`)
39+) ENGINE=InnoDB;
40+
41+SET @znote_add_schema_sql := IF(
42+ (
43+ SELECT COUNT(*)
44+ FROM INFORMATION_SCHEMA.COLUMNS
45+ WHERE TABLE_SCHEMA = DATABASE()
46+ AND TABLE_NAME = 'znote_legacy_tables'
47+ AND COLUMN_NAME = 'schema_sql'
48+ ) = 0,
49+ 'ALTER TABLE `znote_legacy_tables` ADD `schema_sql` longtext NULL AFTER `table_name`',
50+ 'SELECT 1'
51+);
52+PREPARE znote_add_schema_sql_stmt FROM @znote_add_schema_sql;
53+EXECUTE znote_add_schema_sql_stmt;
54+DEALLOCATE PREPARE znote_add_schema_sql_stmt;
55+
56+CREATE TABLE IF NOT EXISTS `znote_legacy_rows` (
57+ `id` bigint NOT NULL AUTO_INCREMENT,
58+ `source` varchar(32) NOT NULL,
59+ `table_name` varchar(64) NOT NULL,
60+ `source_pk` varchar(128) NOT NULL DEFAULT '',
61+ `row_json` longtext NOT NULL,
62+ `captured` int NOT NULL,
63+ PRIMARY KEY (`id`),
64+ KEY `source_table` (`source`, `table_name`),
65+ KEY `source_pk` (`source`, `table_name`, `source_pk`)
66+) ENGINE=InnoDB;
A SQL/migrations/2.0.0_payment_gateways.sql +38-0 View file
@@ -0,0 +1,38 @@
1+-- Stripe / Mercado Pago transaction ledger.
2+-- Safe to run more than once.
3+
4+CREATE TABLE IF NOT EXISTS `znote_payment_transactions` (
5+ `id` bigint NOT NULL AUTO_INCREMENT,
6+ `provider` varchar(32) NOT NULL,
7+ `reference` varchar(128) NOT NULL,
8+ `provider_reference` varchar(128) DEFAULT NULL,
9+ `account_id` int NOT NULL,
10+ `price` decimal(11,2) NOT NULL,
11+ `currency` varchar(8) NOT NULL,
12+ `points` int NOT NULL,
13+ `status` varchar(32) NOT NULL DEFAULT 'pending',
14+ `credited` tinyint NOT NULL DEFAULT '0',
15+ `test_mode` tinyint NOT NULL DEFAULT '0',
16+ `created_at` int NOT NULL,
17+ `updated_at` int NOT NULL,
18+ `credited_at` int DEFAULT NULL,
19+ `payload` longtext,
20+ PRIMARY KEY (`id`),
21+ UNIQUE KEY `provider_reference_internal` (`provider`, `reference`),
22+ KEY `provider_reference_external` (`provider`, `provider_reference`),
23+ KEY `account_status` (`account_id`, `status`, `created_at`)
24+) ENGINE=InnoDB;
25+
26+CREATE TABLE IF NOT EXISTS `znote_payment_events` (
27+ `id` bigint NOT NULL AUTO_INCREMENT,
28+ `provider` varchar(32) NOT NULL,
29+ `event_id` varchar(128) NOT NULL,
30+ `provider_reference` varchar(128) DEFAULT NULL,
31+ `payment_reference` varchar(128) DEFAULT NULL,
32+ `status` varchar(32) NOT NULL DEFAULT 'received',
33+ `payload` longtext,
34+ `received_at` int NOT NULL,
35+ PRIMARY KEY (`id`),
36+ UNIQUE KEY `provider_event` (`provider`, `event_id`),
37+ KEY `payment_reference` (`provider`, `payment_reference`)
38+) ENGINE=InnoDB;
A SQL/migrations/2.0.0_znote_config.sql +25-0 View file
@@ -0,0 +1,25 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.0 - settings table (needed by the layout system)
3+--
4+-- Only needed for databases created BEFORE 2.0.0. A fresh import of
5+-- SQL/znote_schema.sql already contains this table.
6+--
7+-- Key/value settings written from the admin panel. The active layout is the
8+-- first user of it; anything else the panel should be able to change without
9+-- editing config.php belongs here too.
10+--
11+-- Run once:
12+-- mysql -u <user> -p <database> < SQL/migrations/2.0.0_znote_config.sql
13+-- ---------------------------------------------------------------------------
14+
15+CREATE TABLE IF NOT EXISTS `znote_config` (
16+ `key` varchar(64) NOT NULL,
17+ `value` text NOT NULL,
18+ PRIMARY KEY (`key`)
19+) ENGINE=InnoDB;
20+
21+-- Existing sites keep the look they have: 'default' is the current Snavy
22+-- layout, moved from layout/ to layouts/default/.
23+INSERT INTO `znote_config` (`key`, `value`) VALUES
24+('layout', 'default')
25+ON DUPLICATE KEY UPDATE `key` = `key`;
A SQL/migrations/2.0.1_password_hash.sql +10-0 View file
@@ -0,0 +1,10 @@
1+-- Website-only password modernization: accounts.password (SHA1, read by the
2+-- game server) is never touched. This column holds a modern password_hash()
3+-- value used only to verify website logins; it is filled in lazily as each
4+-- account logs in or changes its password.
5+--
6+-- Only needed for databases created BEFORE 2.0.1. A fresh import of
7+-- SQL/znote_schema.sql already contains this column.
8+
9+ALTER TABLE `znote_accounts`
10+ ADD COLUMN `password_hash` varchar(255) DEFAULT NULL AFTER `secret`;
A SQL/migrations/2.0.1_utf8mb4.sql +39-0 View file
@@ -0,0 +1,39 @@
1+-- ZnoteX 2.0.1 - convert ZnoteX-owned tables to full UTF-8.
2+
3+SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;
4+
5+ALTER TABLE `znote` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
6+ALTER TABLE `znote_accounts` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
7+ALTER TABLE `znote_news` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
8+ALTER TABLE `znote_images` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
9+ALTER TABLE `znote_paypal` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
10+ALTER TABLE `znote_paygol` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
11+ALTER TABLE `znote_pagseguro` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
12+ALTER TABLE `znote_pagseguro_notifications` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
13+ALTER TABLE `znote_payment_transactions` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
14+ALTER TABLE `znote_payment_events` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
15+ALTER TABLE `znote_players` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
16+ALTER TABLE `znote_player_reports` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
17+ALTER TABLE `znote_changelog` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
18+ALTER TABLE `znote_shop` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
19+ALTER TABLE `znote_shop_offers` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
20+ALTER TABLE `znote_shop_logs` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
21+ALTER TABLE `znote_shop_orders` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
22+ALTER TABLE `znote_admin_log` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
23+ALTER TABLE `znote_config` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
24+ALTER TABLE `znote_menu` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
25+ALTER TABLE `znote_pages` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
26+ALTER TABLE `znote_convert_map` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
27+ALTER TABLE `znote_legacy_tables` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
28+ALTER TABLE `znote_legacy_rows` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
29+ALTER TABLE `znote_visitors` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
30+ALTER TABLE `znote_visitors_details` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
31+ALTER TABLE `znote_forum` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
32+ALTER TABLE `znote_forum_threads` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
33+ALTER TABLE `znote_forum_posts` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
34+ALTER TABLE `znote_deleted_characters` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
35+ALTER TABLE `znote_guild_wars` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
36+ALTER TABLE `znote_tickets` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
37+ALTER TABLE `znote_tickets_replies` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
38+ALTER TABLE `znote_global_storage` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
39+ALTER TABLE `znote_auction_player` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
A SQL/migrations/2.0.2_twofa_v2.sql +40-0 View file
@@ -0,0 +1,40 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.2 - Website 2FA v2
3+--
4+-- Independent of the game engine: nothing here touches `accounts`, so it works
5+-- the same on TFS, Canary, otHire or BlackTek. The old TFS-tied system
6+-- (accounts.secret, znote_accounts.secret) is untouched and keeps working.
7+-- ---------------------------------------------------------------------------
8+
9+CREATE TABLE IF NOT EXISTS `znote_2fa` (
10+ `account_id` int NOT NULL,
11+ `totp_secret` varchar(64) DEFAULT NULL,
12+ `totp_enabled` tinyint(1) NOT NULL DEFAULT '0',
13+ `email_otp_enabled` tinyint(1) NOT NULL DEFAULT '0',
14+ `recovery_codes` text,
15+ `session_version` int NOT NULL DEFAULT '1',
16+ `updated_at` int NOT NULL DEFAULT '0',
17+ PRIMARY KEY (`account_id`)
18+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
19+
20+CREATE TABLE IF NOT EXISTS `znote_2fa_email_codes` (
21+ `account_id` int NOT NULL,
22+ `code_hash` varchar(64) NOT NULL,
23+ `expires_at` int NOT NULL,
24+ `attempts` int NOT NULL DEFAULT '0',
25+ PRIMARY KEY (`account_id`)
26+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
27+
28+CREATE TABLE IF NOT EXISTS `znote_2fa_trusted_devices` (
29+ `id` int NOT NULL AUTO_INCREMENT,
30+ `account_id` int NOT NULL,
31+ `token_hash` varchar(64) NOT NULL,
32+ `label` varchar(255) NOT NULL DEFAULT '',
33+ `ip` varchar(45) NOT NULL DEFAULT '',
34+ `created_at` int NOT NULL,
35+ `expires_at` int NOT NULL,
36+ `last_used_at` int NOT NULL DEFAULT '0',
37+ PRIMARY KEY (`id`),
38+ UNIQUE KEY `token_hash` (`token_hash`),
39+ KEY `account_id` (`account_id`)
40+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
A SQL/migrations/2.0.3_login_attempts.sql +18-0 View file
@@ -0,0 +1,18 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.3 - Login attempt tracking / IP lockout
3+--
4+-- Every login attempt (success or failure) is logged by IP. Admin Panel >
5+-- Security > Login Protection reads this to lock an IP out after too many
6+-- failures in a short window. Rows are pruned automatically as new ones are
7+-- written, so this table never grows without bound.
8+-- ---------------------------------------------------------------------------
9+
10+CREATE TABLE IF NOT EXISTS `znote_login_attempts` (
11+ `id` int NOT NULL AUTO_INCREMENT,
12+ `ip` varchar(45) NOT NULL,
13+ `username` varchar(32) NOT NULL DEFAULT '',
14+ `success` tinyint(1) NOT NULL DEFAULT '0',
15+ `created_at` int NOT NULL,
16+ PRIMARY KEY (`id`),
17+ KEY `ip_time` (`ip`, `created_at`)
18+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
A SQL/migrations/2.0.4_serverdata_overrides.sql +22-0 View file
@@ -0,0 +1,22 @@
1+-- ---------------------------------------------------------------------------
2+-- ZnoteX 2.0.4 - Server data overrides (config / items / creatures editors)
3+--
4+-- admin/modules/config_editor.php, items_editor.php and creatures_editor.php
5+-- let admins edit or add single records on top of the bulk-uploaded
6+-- config.lua / items.xml / monster files handled by admin/modules/serverinfo.php.
7+-- Overrides are stored here and merged over the parsed cache at read time
8+-- (serverdata_apply_overrides(), called from serverdata_load()), so a later
9+-- re-upload of the source file never discards a manual edit.
10+-- ---------------------------------------------------------------------------
11+
12+CREATE TABLE IF NOT EXISTS `znote_serverdata_overrides` (
13+ `id` int NOT NULL AUTO_INCREMENT,
14+ `source` varchar(16) NOT NULL,
15+ `record_key` varchar(191) NOT NULL,
16+ `data` text NOT NULL,
17+ `deleted` tinyint(1) NOT NULL DEFAULT '0',
18+ `updated_by` varchar(64) DEFAULT NULL,
19+ `updated_at` int NOT NULL,
20+ PRIMARY KEY (`id`),
21+ UNIQUE KEY `source_key` (`source`, `record_key`)
22+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
A SQL/znote_schema.sql +559-0 View file
@@ -0,0 +1,559 @@
1+-- Start of Znote AAC database schema
2+
3+SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;
4+
5+SET @znote_version = '2.0.0';
6+
7+CREATE TABLE IF NOT EXISTS `znote` (
8+ `id` int NOT NULL AUTO_INCREMENT,
9+ `version` varchar(30) NOT NULL COMMENT 'Znote AAC version',
10+ `installed` int NOT NULL,
11+ `cached` int DEFAULT NULL,
12+ PRIMARY KEY (`id`)
13+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
14+
15+CREATE TABLE IF NOT EXISTS `znote_accounts` (
16+ `id` int NOT NULL AUTO_INCREMENT,
17+ `account_id` int NOT NULL,
18+ `ip` bigint UNSIGNED NOT NULL,
19+ `created` int NOT NULL,
20+ `points` int DEFAULT 0,
21+ `cooldown` int DEFAULT 0,
22+ `active` tinyint NOT NULL DEFAULT '0',
23+ `active_email` tinyint NOT NULL DEFAULT '0',
24+ `activekey` int NOT NULL DEFAULT '0',
25+ `flag` varchar(20) NOT NULL,
26+ `secret` char(16) DEFAULT NULL,
27+ `password_hash` varchar(255) DEFAULT NULL,
28+ PRIMARY KEY (`id`)
29+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
30+
31+CREATE TABLE IF NOT EXISTS `znote_news` (
32+ `id` int NOT NULL AUTO_INCREMENT,
33+ `title` varchar(30) NOT NULL,
34+ `text` text NOT NULL,
35+ `date` int NOT NULL,
36+ `pid` int NOT NULL,
37+ PRIMARY KEY (`id`)
38+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
39+
40+CREATE TABLE IF NOT EXISTS `znote_images` (
41+ `id` int NOT NULL AUTO_INCREMENT,
42+ `title` varchar(30) NOT NULL,
43+ `desc` text NOT NULL,
44+ `date` int NOT NULL,
45+ `status` int NOT NULL,
46+ `image` varchar(255) NOT NULL,
47+ `delhash` varchar(30) NOT NULL,
48+ `account_id` int NOT NULL,
49+ PRIMARY KEY (`id`)
50+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
51+
52+CREATE TABLE IF NOT EXISTS `znote_paypal` (
53+ `id` int NOT NULL AUTO_INCREMENT,
54+ `txn_id` varchar(30) NOT NULL,
55+ `email` varchar(255) NOT NULL,
56+ `accid` int NOT NULL,
57+ `price` int NOT NULL,
58+ `points` int NOT NULL,
59+ PRIMARY KEY (`id`)
60+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
61+
62+CREATE TABLE IF NOT EXISTS `znote_paygol` (
63+ `id` int NOT NULL AUTO_INCREMENT,
64+ `account_id` int NOT NULL,
65+ `price` int NOT NULL,
66+ `points` int NOT NULL,
67+ `message_id` varchar(255) NOT NULL,
68+ `service_id` varchar(255) NOT NULL,
69+ `shortcode` varchar(255) NOT NULL,
70+ `keyword` varchar(255) NOT NULL,
71+ `message` varchar(255) NOT NULL,
72+ `sender` varchar(255) NOT NULL,
73+ `operator` varchar(255) NOT NULL,
74+ `country` varchar(255) NOT NULL,
75+ `currency` varchar(255) NOT NULL,
76+ PRIMARY KEY (`id`)
77+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
78+
79+CREATE TABLE IF NOT EXISTS `znote_pagseguro` (
80+ `id` int NOT NULL AUTO_INCREMENT,
81+ `transaction` varchar(36) NOT NULL,
82+ `account` int NOT NULL,
83+ `price` decimal(11,2) NOT NULL,
84+ `points` int NOT NULL,
85+ `payment_status` tinyint NOT NULL,
86+ `completed` tinyint NOT NULL,
87+ PRIMARY KEY (`id`),
88+ KEY `transaction` (`transaction`)
89+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
90+
91+CREATE TABLE IF NOT EXISTS `znote_pagseguro_notifications` (
92+ `id` int NOT NULL AUTO_INCREMENT,
93+ `notification_code` varchar(40) NOT NULL,
94+ `details` text NOT NULL,
95+ `receive_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
96+ PRIMARY KEY (`id`)
97+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
98+
99+CREATE TABLE IF NOT EXISTS `znote_payment_transactions` (
100+ `id` bigint NOT NULL AUTO_INCREMENT,
101+ `provider` varchar(32) NOT NULL,
102+ `reference` varchar(128) NOT NULL,
103+ `provider_reference` varchar(128) DEFAULT NULL,
104+ `account_id` int NOT NULL,
105+ `price` decimal(11,2) NOT NULL,
106+ `currency` varchar(8) NOT NULL,
107+ `points` int NOT NULL,
108+ `status` varchar(32) NOT NULL DEFAULT 'pending',
109+ `credited` tinyint NOT NULL DEFAULT '0',
110+ `test_mode` tinyint NOT NULL DEFAULT '0',
111+ `created_at` int NOT NULL,
112+ `updated_at` int NOT NULL,
113+ `credited_at` int DEFAULT NULL,
114+ `payload` longtext,
115+ PRIMARY KEY (`id`),
116+ UNIQUE KEY `provider_reference_internal` (`provider`, `reference`),
117+ KEY `provider_reference_external` (`provider`, `provider_reference`),
118+ KEY `account_status` (`account_id`, `status`, `created_at`)
119+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
120+
121+CREATE TABLE IF NOT EXISTS `znote_payment_events` (
122+ `id` bigint NOT NULL AUTO_INCREMENT,
123+ `provider` varchar(32) NOT NULL,
124+ `event_id` varchar(128) NOT NULL,
125+ `provider_reference` varchar(128) DEFAULT NULL,
126+ `payment_reference` varchar(128) DEFAULT NULL,
127+ `status` varchar(32) NOT NULL DEFAULT 'received',
128+ `payload` longtext,
129+ `received_at` int NOT NULL,
130+ PRIMARY KEY (`id`),
131+ UNIQUE KEY `provider_event` (`provider`, `event_id`),
132+ KEY `payment_reference` (`provider`, `payment_reference`)
133+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
134+
135+CREATE TABLE IF NOT EXISTS `znote_players` (
136+ `id` int NOT NULL AUTO_INCREMENT,
137+ `player_id` int NOT NULL,
138+ `created` int NOT NULL,
139+ `hide_char` tinyint NOT NULL,
140+ `comment` varchar(255) NOT NULL,
141+ PRIMARY KEY (`id`)
142+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
143+
144+CREATE TABLE IF NOT EXISTS `znote_player_reports` (
145+ `id` int NOT NULL AUTO_INCREMENT,
146+ `name` varchar(50) NOT NULL,
147+ `posx` int NOT NULL,
148+ `posy` int NOT NULL,
149+ `posz` int NOT NULL,
150+ `report_description` varchar(255) NOT NULL,
151+ `date` int NOT NULL,
152+ `status` tinyint NOT NULL DEFAULT '0',
153+ PRIMARY KEY (`id`)
154+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
155+
156+CREATE TABLE IF NOT EXISTS `znote_changelog` (
157+ `id` int NOT NULL AUTO_INCREMENT,
158+ `text` varchar(255) NOT NULL,
159+ `time` int NOT NULL,
160+ `report_id` int NOT NULL,
161+ `status` tinyint NOT NULL DEFAULT '0',
162+ PRIMARY KEY (`id`)
163+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
164+
165+CREATE TABLE IF NOT EXISTS `znote_shop` (
166+ `id` int NOT NULL AUTO_INCREMENT,
167+ `type` int NOT NULL,
168+ `itemid` int DEFAULT NULL,
169+ `count` int NOT NULL DEFAULT '1',
170+ `description` varchar(255) NOT NULL,
171+ `points` int NOT NULL DEFAULT '10',
172+ PRIMARY KEY (`id`)
173+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
174+
175+CREATE TABLE IF NOT EXISTS `znote_shop_offers` (
176+ `id` int NOT NULL AUTO_INCREMENT,
177+ `type` int NOT NULL,
178+ `itemid` int DEFAULT NULL,
179+ `count` int NOT NULL DEFAULT '1',
180+ `description` varchar(255) NOT NULL,
181+ `points` int NOT NULL DEFAULT '10',
182+ `active` tinyint NOT NULL DEFAULT '1',
183+ `sort_order` int NOT NULL DEFAULT '0',
184+ `created_by` int DEFAULT NULL,
185+ `created_at` int DEFAULT NULL,
186+ `updated_at` int DEFAULT NULL,
187+ PRIMARY KEY (`id`),
188+ KEY `active_sort` (`active`, `sort_order`, `id`)
189+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
190+
191+CREATE TABLE IF NOT EXISTS `znote_shop_logs` (
192+ `id` int NOT NULL AUTO_INCREMENT,
193+ `account_id` int NOT NULL,
194+ `player_id` int NOT NULL,
195+ `type` int NOT NULL,
196+ `itemid` int NOT NULL,
197+ `count` int NOT NULL,
198+ `points` int NOT NULL,
199+ `time` int NOT NULL,
200+ PRIMARY KEY (`id`)
201+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
202+
203+CREATE TABLE IF NOT EXISTS `znote_shop_orders` (
204+ `id` int NOT NULL AUTO_INCREMENT,
205+ `account_id` int NOT NULL,
206+ `type` int NOT NULL,
207+ `itemid` int NOT NULL,
208+ `count` int NOT NULL,
209+ `time` int NOT NULL DEFAULT '0',
210+ PRIMARY KEY (`id`)
211+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
212+
213+-- Audit trail of mutating actions taken from the admin panel: bans, skill
214+-- edits, points, settings changes, plugin lifecycle, etc. Written by
215+-- acp_log() in engine/function/adminlog.php, read by Admin Panel > Admin Log.
216+CREATE TABLE IF NOT EXISTS `znote_admin_log` (
217+ `id` int NOT NULL AUTO_INCREMENT,
218+ `admin_id` int NOT NULL DEFAULT '0',
219+ `admin_name` varchar(50) NOT NULL DEFAULT '',
220+ `action` varchar(64) NOT NULL,
221+ `target` varchar(191) NOT NULL DEFAULT '',
222+ `details` text NOT NULL,
223+ `ip` varchar(45) NOT NULL DEFAULT '',
224+ `created` int NOT NULL,
225+ PRIMARY KEY (`id`),
226+ KEY `admin_created` (`admin_id`, `created`),
227+ KEY `action_created` (`action`, `created`),
228+ KEY `created` (`created`)
229+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
230+
231+-- Key/value settings written from the admin panel (active layout, etc).
232+CREATE TABLE IF NOT EXISTS `znote_config` (
233+ `key` varchar(64) NOT NULL,
234+ `value` text NOT NULL,
235+ PRIMARY KEY (`key`)
236+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
237+
238+-- Navigation entries, managed from Admin Panel > Menus.
239+-- A theme declares the locations it renders (see layouts/README.md); entries
240+-- are grouped by that location and ordered by sort_order.
241+CREATE TABLE IF NOT EXISTS `znote_menu` (
242+ `id` int NOT NULL AUTO_INCREMENT,
243+ `location` varchar(32) NOT NULL COMMENT 'Theme-declared slot: main, sidebar, footer...',
244+ `parent_id` int NOT NULL DEFAULT '0' COMMENT '0 = top level, else the id of the parent entry',
245+ `label` varchar(64) NOT NULL,
246+ `url` varchar(255) NOT NULL,
247+ `icon` varchar(48) NOT NULL DEFAULT '' COMMENT 'Optional Font Awesome class',
248+ `target` varchar(10) NOT NULL DEFAULT '',
249+ `visibility` varchar(10) NOT NULL DEFAULT 'all' COMMENT 'all, guest, user or admin',
250+ `sort_order` int NOT NULL DEFAULT '0',
251+ `active` tinyint NOT NULL DEFAULT '1',
252+ PRIMARY KEY (`id`),
253+ KEY `loc_sort` (`location`, `active`, `sort_order`, `id`)
254+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
255+
256+-- Database-backed pages, used by importers and editable site content.
257+CREATE TABLE IF NOT EXISTS `znote_pages` (
258+ `id` int NOT NULL AUTO_INCREMENT,
259+ `slug` varchar(64) NOT NULL,
260+ `title` varchar(100) NOT NULL,
261+ `body` mediumtext NOT NULL,
262+ `created` int NOT NULL DEFAULT '0',
263+ `updated` int NOT NULL DEFAULT '0',
264+ `player_id` int NOT NULL DEFAULT '0',
265+ `access` tinyint NOT NULL DEFAULT '0',
266+ `active` tinyint NOT NULL DEFAULT '1',
267+ PRIMARY KEY (`id`),
268+ UNIQUE KEY `slug` (`slug`)
269+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
270+
271+-- Keeps legacy imports idempotent and stores source ids for follow-up imports.
272+CREATE TABLE IF NOT EXISTS `znote_convert_map` (
273+ `id` int NOT NULL AUTO_INCREMENT,
274+ `source` varchar(32) NOT NULL,
275+ `source_table` varchar(64) NOT NULL,
276+ `source_id` varchar(64) NOT NULL,
277+ `target_table` varchar(64) NOT NULL,
278+ `target_id` int NOT NULL,
279+ `created` int NOT NULL,
280+ PRIMARY KEY (`id`),
281+ UNIQUE KEY `source_row` (`source`, `source_table`, `source_id`, `target_table`)
282+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
283+
284+-- Raw archive of legacy AAC tables, including custom columns ZnoteX does not
285+-- understand yet. This keeps migrations lossless.
286+CREATE TABLE IF NOT EXISTS `znote_legacy_tables` (
287+ `id` int NOT NULL AUTO_INCREMENT,
288+ `source` varchar(32) NOT NULL,
289+ `table_name` varchar(64) NOT NULL,
290+ `schema_sql` longtext NOT NULL,
291+ `row_count` int NOT NULL DEFAULT '0',
292+ `captured` int NOT NULL,
293+ PRIMARY KEY (`id`),
294+ UNIQUE KEY `source_table` (`source`, `table_name`)
295+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
296+
297+CREATE TABLE IF NOT EXISTS `znote_legacy_rows` (
298+ `id` bigint NOT NULL AUTO_INCREMENT,
299+ `source` varchar(32) NOT NULL,
300+ `table_name` varchar(64) NOT NULL,
301+ `source_pk` varchar(128) NOT NULL DEFAULT '',
302+ `row_json` longtext NOT NULL,
303+ `captured` int NOT NULL,
304+ PRIMARY KEY (`id`),
305+ KEY `source_table` (`source`, `table_name`),
306+ KEY `source_pk` (`source`, `table_name`, `source_pk`)
307+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
308+
309+CREATE TABLE IF NOT EXISTS `znote_visitors` (
310+ `id` int NOT NULL AUTO_INCREMENT,
311+ `ip` bigint NOT NULL,
312+ `value` int NOT NULL,
313+ PRIMARY KEY (`id`)
314+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
315+
316+CREATE TABLE IF NOT EXISTS `znote_visitors_details` (
317+ `id` int NOT NULL AUTO_INCREMENT,
318+ `ip` bigint NOT NULL,
319+ `time` int NOT NULL,
320+ `type` tinyint NOT NULL,
321+ `account_id` int NOT NULL,
322+ PRIMARY KEY (`id`)
323+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
324+
325+-- Forum 1/3 (boards)
326+CREATE TABLE IF NOT EXISTS `znote_forum` (
327+ `id` int NOT NULL AUTO_INCREMENT,
328+ `name` varchar(50) NOT NULL,
329+ `access` tinyint NOT NULL,
330+ `closed` tinyint NOT NULL,
331+ `hidden` tinyint NOT NULL,
332+ `guild_id` int NOT NULL,
333+ PRIMARY KEY (`id`)
334+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
335+
336+-- Forum 2/3 (threads)
337+CREATE TABLE IF NOT EXISTS `znote_forum_threads` (
338+ `id` int NOT NULL AUTO_INCREMENT,
339+ `forum_id` int NOT NULL,
340+ `player_id` int NOT NULL,
341+ `player_name` varchar(50) NOT NULL,
342+ `title` varchar(50) NOT NULL,
343+ `text` text NOT NULL,
344+ `created` int NOT NULL,
345+ `updated` int NOT NULL,
346+ `sticky` tinyint NOT NULL,
347+ `hidden` tinyint NOT NULL,
348+ `closed` tinyint NOT NULL,
349+ PRIMARY KEY (`id`)
350+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
351+
352+-- Forum 3/3 (posts)
353+CREATE TABLE IF NOT EXISTS `znote_forum_posts` (
354+ `id` int NOT NULL AUTO_INCREMENT,
355+ `thread_id` int NOT NULL,
356+ `player_id` int NOT NULL,
357+ `player_name` varchar(50) NOT NULL,
358+ `text` text NOT NULL,
359+ `created` int NOT NULL,
360+ `updated` int NOT NULL,
361+ PRIMARY KEY (`id`)
362+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
363+
364+-- Pending characters for deletion
365+CREATE TABLE IF NOT EXISTS `znote_deleted_characters` (
366+ `id` int NOT NULL AUTO_INCREMENT,
367+ `original_account_id` int NOT NULL,
368+ `character_name` varchar(255) NOT NULL,
369+ `time` datetime NOT NULL,
370+ `done` tinyint NOT NULL,
371+ PRIMARY KEY (`id`)
372+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
373+
374+CREATE TABLE IF NOT EXISTS `znote_guild_wars` (
375+ `id` int NOT NULL AUTO_INCREMENT,
376+ `limit` int NOT NULL DEFAULT '0',
377+ PRIMARY KEY (`id`)
378+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
379+
380+-- Helpdesk system
381+CREATE TABLE IF NOT EXISTS `znote_tickets` (
382+ `id` int NOT NULL AUTO_INCREMENT,
383+ `owner` int NOT NULL,
384+ `username` varchar(32) NOT NULL,
385+ `subject` text NOT NULL,
386+ `message` text NOT NULL,
387+ `ip` bigint NOT NULL,
388+ `creation` int NOT NULL,
389+ `status` varchar(20) NOT NULL,
390+ PRIMARY KEY (`id`)
391+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
392+
393+CREATE TABLE IF NOT EXISTS `znote_tickets_replies` (
394+ `id` int NOT NULL AUTO_INCREMENT,
395+ `tid` int NOT NULL,
396+ `username` varchar(32) NOT NULL,
397+ `message` text NOT NULL,
398+ `created` int NOT NULL,
399+ PRIMARY KEY (`id`)
400+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
401+
402+CREATE TABLE IF NOT EXISTS `znote_global_storage` (
403+ `key` varchar(32) NOT NULL,
404+ `value` TEXT NOT NULL,
405+ UNIQUE (`key`)
406+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
407+
408+-- Character auction system
409+CREATE TABLE IF NOT EXISTS `znote_auction_player` (
410+ `id` int NOT NULL AUTO_INCREMENT,
411+ `player_id` int NOT NULL,
412+ `original_account_id` int NOT NULL,
413+ `bidder_account_id` int NOT NULL,
414+ `time_begin` int NOT NULL,
415+ `time_end` int NOT NULL,
416+ `price` int NOT NULL,
417+ `bid` int NOT NULL,
418+ `deposit` int NOT NULL,
419+ `sold` tinyint NOT NULL,
420+ `claimed` tinyint NOT NULL,
421+ PRIMARY KEY (`id`)
422+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
423+
424+-- Populate basic info
425+INSERT INTO `znote` (`version`, `installed`) VALUES
426+(@znote_version, UNIX_TIMESTAMP(CURDATE()));
427+
428+-- Default settings
429+INSERT INTO `znote_config` (`key`, `value`) VALUES
430+('layout', 'default');
431+
432+INSERT INTO `znote_menu` (`location`, `parent_id`, `label`, `url`, `icon`, `visibility`, `sort_order`)
433+SELECT `seed`.`location`, `seed`.`parent_id`, `seed`.`label`, `seed`.`url`, `seed`.`icon`, `seed`.`visibility`, `seed`.`sort_order`
434+FROM (
435+ SELECT 'main' AS `location`, 0 AS `parent_id`, 'Home' AS `label`, 'index.php' AS `url`, 'fa-home' AS `icon`, 'all' AS `visibility`, 10 AS `sort_order`
436+ UNION ALL SELECT 'main', 0, 'Changelog', 'changelog.php', '', 'all', 20
437+ UNION ALL SELECT 'main', 0, 'Account', 'myaccount.php', 'fa-user-circle', 'user', 30
438+ UNION ALL SELECT 'main', 0, 'Login', 'login.php', 'fa-user-circle', 'guest', 30
439+ UNION ALL SELECT 'main', 0, 'Register', 'register.php', 'fa-key', 'guest', 40
440+ UNION ALL SELECT 'main', 0, 'Downloads', 'downloads.php', '', 'all', 50
441+ UNION ALL SELECT 'main', 0, 'Community', 'onlinelist.php', 'fa-users', 'all', 60
442+ UNION ALL SELECT 'main', 0, 'Highscores', 'highscores.php', '', 'all', 70
443+ UNION ALL SELECT 'main', 0, 'Guilds', 'guilds.php', '', 'all', 80
444+ UNION ALL SELECT 'main', 0, 'Forum', 'forum.php', '', 'all', 90
445+ UNION ALL SELECT 'main', 0, 'Houses', 'houses.php', '', 'all', 100
446+ UNION ALL SELECT 'main', 0, 'Latest deaths', 'deaths.php', '', 'all', 110
447+ UNION ALL SELECT 'main', 0, 'Kill statistics', 'killers.php', '', 'all', 120
448+ UNION ALL SELECT 'main', 0, 'Bans', 'bans.php', '', 'all', 125
449+ UNION ALL SELECT 'main', 0, 'Creatures', 'creatures.php', '', 'all', 135
450+ UNION ALL SELECT 'main', 0, 'Library', 'serverinfo.php', 'fa-book', 'all', 130
451+ UNION ALL SELECT 'main', 0, 'Spells', 'spells.php', '', 'all', 140
452+ UNION ALL SELECT 'main', 0, 'Support', 'support.php', 'fa-info-circle', 'all', 150
453+ UNION ALL SELECT 'main', 0, 'Helpdesk', 'helpdesk.php', '', 'all', 160
454+ UNION ALL SELECT 'main', 0, 'Shop', 'shop.php', 'fa-shopping-cart', 'all', 170
455+ UNION ALL SELECT 'main', 0, 'Buy points', 'buypoints.php', '', 'all', 180
456+ UNION ALL SELECT 'main', 0, 'Admin Panel', 'admin/index.php', 'fa-sliders', 'admin', 190
457+) `seed`
458+LEFT JOIN `znote_menu` `existing`
459+ ON `existing`.`location` = `seed`.`location`
460+ AND `existing`.`label` = `seed`.`label`
461+ AND `existing`.`url` = `seed`.`url`
462+WHERE `existing`.`id` IS NULL;
463+
464+
465+-- Nest the sub-entries under their section. Done as a second pass because the
466+-- parent ids are only known once the rows above exist.
467+UPDATE `znote_menu` `c`
468+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Home'
469+ SET `c`.`parent_id` = `p`.`id`
470+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Changelog');
471+
472+UPDATE `znote_menu` `c`
473+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Account'
474+ SET `c`.`parent_id` = `p`.`id`
475+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Downloads');
476+
477+UPDATE `znote_menu` `c`
478+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Community'
479+ SET `c`.`parent_id` = `p`.`id`
480+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Highscores','Guilds','Forum','Houses','Latest deaths','Kill statistics','Bans');
481+
482+UPDATE `znote_menu` `c`
483+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Library'
484+ SET `c`.`parent_id` = `p`.`id`
485+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Spells','Creatures');
486+
487+UPDATE `znote_menu` `c`
488+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Support'
489+ SET `c`.`parent_id` = `p`.`id`
490+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Helpdesk');
491+
492+UPDATE `znote_menu` `c`
493+ JOIN `znote_menu` `p` ON `p`.`location` = 'main' AND `p`.`parent_id` = 0 AND `p`.`label` = 'Shop'
494+ SET `c`.`parent_id` = `p`.`id`
495+ WHERE `c`.`location` = 'main' AND `c`.`label` IN ('Buy points');
496+
497+-- Add default forum boards
498+INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`)
499+SELECT 'Staff Board', '4', '0', '0', '0' FROM DUAL
500+WHERE NOT EXISTS (SELECT 1 FROM `znote_forum` WHERE `name` = 'Staff Board' AND `guild_id` = '0');
501+INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`)
502+SELECT 'Tutors Board', '2', '0', '0', '0' FROM DUAL
503+WHERE NOT EXISTS (SELECT 1 FROM `znote_forum` WHERE `name` = 'Tutors Board' AND `guild_id` = '0');
504+INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`)
505+SELECT 'Discussion', '1', '0', '0', '0' FROM DUAL
506+WHERE NOT EXISTS (SELECT 1 FROM `znote_forum` WHERE `name` = 'Discussion' AND `guild_id` = '0');
507+INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`)
508+SELECT 'Feedback', '1', '0', '1', '0' FROM DUAL
509+WHERE NOT EXISTS (SELECT 1 FROM `znote_forum` WHERE `name` = 'Feedback' AND `guild_id` = '0');
510+
511+-- Convert existing accounts in database to be Znote AAC compatible
512+INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `flag`)
513+SELECT
514+ `a`.`id` AS `account_id`,
515+ 0 AS `ip`,
516+ UNIX_TIMESTAMP(CURDATE()) AS `created`,
517+ '' AS `flag`
518+FROM `accounts` AS `a`
519+LEFT JOIN `znote_accounts` AS `z`
520+ ON `a`.`id` = `z`.`account_id`
521+WHERE `z`.`created` IS NULL;
522+
523+-- Convert existing players in database to be Znote AAC compatible
524+INSERT INTO `znote_players` (`player_id`, `created`, `hide_char`, `comment`)
525+SELECT
526+ `p`.`id` AS `player_id`,
527+ UNIX_TIMESTAMP(CURDATE()) AS `created`,
528+ 0 AS `hide_char`,
529+ '' AS `comment`
530+FROM `players` AS `p`
531+LEFT JOIN `znote_players` AS `z`
532+ ON `p`.`id` = `z`.`player_id`
533+WHERE `z`.`created` IS NULL;
534+
535+-- Delete duplicate account records
536+DELETE `d` FROM `znote_accounts` AS `d`
537+INNER JOIN (
538+ SELECT `i`.`account_id`,
539+ MAX(`i`.`id`) AS `retain`
540+ FROM `znote_accounts` AS `i`
541+ GROUP BY `i`.`account_id`
542+ HAVING COUNT(`i`.`id`) > 1
543+) AS `x`
544+ ON `d`.`account_id` = `x`.`account_id`
545+ AND `d`.`id` != `x`.`retain`;
546+
547+-- Delete duplicate player records
548+DELETE `d` FROM `znote_players` AS `d`
549+INNER JOIN (
550+ SELECT `i`.`player_id`,
551+ MAX(`i`.`id`) AS `retain`
552+ FROM `znote_players` AS `i`
553+ GROUP BY `i`.`player_id`
554+ HAVING COUNT(`i`.`id`) > 1
555+) AS `x`
556+ ON `d`.`player_id` = `x`.`player_id`
557+ AND `d`.`id` != `x`.`retain`;
558+
559+-- End of Znote AAC database schema
A sub.php +10-0 View file
@@ -0,0 +1,10 @@
1+<?php require_once 'engine/init.php'; theme_open();
2+if ($config['allowSubPages']) {
3+ $page = (isset($_GET['page']) && !empty($_GET['page'])) ? getValue($_GET['page'] ?? null) : '';
4+ if (isset($subpages[$page]['file'])) { $f = theme_file('sub/'.$subpages[$page]['file']); if ($f !== null) require_once $f; }
5+ else {
6+ if (isset($subpages)) echo '<h2>'. t('sub.unknown_title') .'</h2><p>'. t('sub.unknown_text') .'</p>';
7+ }
8+}
9+else echo '<h2>'. t('sub.disabled_title') .'</h2><p>'. t('sub.disabled_text') .'</p>';
10+theme_close(); ?>
Top