shop.php

main 189 lines · 5.9 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php require_once 'engine/init.php';
2znote_csrf_protect_public_post();
3theme_open();
4
5if (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'];
11if ($shop['loginToView'] === true) protect_page();
12$loggedin = user_logged_in();
13
14function 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
29function 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
70if ($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
186view('shop');
187
188theme_close();
189
Top