mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-07-17 18:16:09 +00:00
Add (Admin Panel) : Multi-account detection, Push protection, Heatmap, Gold Shop and Blacklist user names
Add (Admin Panel) : Multi-account detection, Push protection, Heatmap, Gold Shop (redem codes), Blacklist user names and Quest Editor + fix some php notice Fix quest_core for mission 33,36, train 2 units, etc (NEW TRIBE) There are some temporary bugs that will be fixed in next commit (pagination for new heatmap, quest reward)
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : GoldShop.php ##
|
||||
## Type : Gold shop / promo-code engine ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Developed by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
|
||||
/**
|
||||
* GoldShop
|
||||
* -------------------------------------------------------------------------
|
||||
* Promo / voucher codes redeemable by players for gold. Admin creates codes
|
||||
* (fixed gold amount, optional max-uses, optional per-user-once, optional
|
||||
* expiry); players redeem them on the Plus page.
|
||||
*
|
||||
* "Free gold for bug hunters" is already covered by the existing admin pages
|
||||
* (Give Free Gold To Specific User / To All) — a single-use code created here
|
||||
* is also a convenient way to hand a bug hunter their reward.
|
||||
*
|
||||
* Self-contained (static, resolves DB link + optional Database from globals,
|
||||
* self-creates its tables). Use-claiming is atomic (conditional UPDATE) so a
|
||||
* limited code cannot be over-redeemed under concurrency.
|
||||
*/
|
||||
class GoldShop
|
||||
{
|
||||
private static function link()
|
||||
{
|
||||
if (isset($GLOBALS['link']) && $GLOBALS['link']) {
|
||||
return $GLOBALS['link'];
|
||||
}
|
||||
if (isset($GLOBALS['database']) && isset($GLOBALS['database']->dblink)) {
|
||||
return $GLOBALS['database']->dblink;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function ensureSchema()
|
||||
{
|
||||
$link = self::link();
|
||||
if (!$link) {
|
||||
return;
|
||||
}
|
||||
@mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "gold_promo` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(64) NOT NULL,
|
||||
`gold` int(11) NOT NULL DEFAULT 0,
|
||||
`max_uses` int(11) NOT NULL DEFAULT 0,
|
||||
`uses` int(11) NOT NULL DEFAULT 0,
|
||||
`per_user` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`expires` int(11) NOT NULL DEFAULT 0,
|
||||
`active` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`note` varchar(255) NOT NULL DEFAULT '',
|
||||
`created_by` int(11) NOT NULL DEFAULT 0,
|
||||
`time` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8");
|
||||
|
||||
@mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "gold_promo_redeem` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`promo_id` int(11) NOT NULL DEFAULT 0,
|
||||
`uid` int(11) NOT NULL DEFAULT 0,
|
||||
`gold` int(11) NOT NULL DEFAULT 0,
|
||||
`time` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `promo_uid` (`promo_id`,`uid`),
|
||||
KEY `uid` (`uid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8");
|
||||
}
|
||||
|
||||
/** Normalise a code: trim, uppercase, collapse to allowed charset. */
|
||||
public static function normCode($code)
|
||||
{
|
||||
$code = strtoupper(trim((string) $code));
|
||||
return preg_replace('/[^A-Z0-9._-]/', '', $code);
|
||||
}
|
||||
|
||||
/* ---- Admin management ---------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a promo code.
|
||||
* @return array [ok(bool), message(string)]
|
||||
*/
|
||||
public static function createCode($code, $gold, $maxUses, $perUser, $expires, $note, $adminId)
|
||||
{
|
||||
$link = self::link();
|
||||
if (!$link) {
|
||||
return [false, 'No database connection.'];
|
||||
}
|
||||
self::ensureSchema();
|
||||
|
||||
$code = self::normCode($code);
|
||||
if ($code === '' || strlen($code) < 3) {
|
||||
return [false, 'Code must be at least 3 characters (A-Z 0-9 . _ -).'];
|
||||
}
|
||||
$gold = (int) $gold;
|
||||
if ($gold <= 0) {
|
||||
return [false, 'Gold amount must be positive.'];
|
||||
}
|
||||
$maxUses = max(0, (int) $maxUses);
|
||||
$perUser = $perUser ? 1 : 0;
|
||||
$expires = max(0, (int) $expires);
|
||||
$note = substr((string) $note, 0, 255);
|
||||
$admin = (int) $adminId;
|
||||
$now = time();
|
||||
|
||||
$stmt = mysqli_prepare($link,
|
||||
"INSERT INTO `" . TB_PREFIX . "gold_promo`
|
||||
(code, gold, max_uses, uses, per_user, expires, active, note, created_by, time)
|
||||
VALUES (?,?,?,0,?,?,1,?,?,?)");
|
||||
if (!$stmt) {
|
||||
return [false, 'Could not prepare statement.'];
|
||||
}
|
||||
mysqli_stmt_bind_param($stmt, 'siiiisii',
|
||||
$code, $gold, $maxUses, $perUser, $expires, $note, $admin, $now);
|
||||
$ok = mysqli_stmt_execute($stmt);
|
||||
$err = mysqli_stmt_errno($stmt);
|
||||
mysqli_stmt_close($stmt);
|
||||
|
||||
if (!$ok) {
|
||||
if ($err === 1062) {
|
||||
return [false, 'A code with that name already exists.'];
|
||||
}
|
||||
return [false, 'Could not create code.'];
|
||||
}
|
||||
return [true, 'Code "' . $code . '" created.'];
|
||||
}
|
||||
|
||||
/** All codes, newest first, each with a resolved status string. */
|
||||
public static function listCodes()
|
||||
{
|
||||
$link = self::link();
|
||||
$out = [];
|
||||
if (!$link) {
|
||||
return $out;
|
||||
}
|
||||
self::ensureSchema();
|
||||
$r = @mysqli_query($link,
|
||||
"SELECT * FROM `" . TB_PREFIX . "gold_promo` ORDER BY `time` DESC, id DESC");
|
||||
if ($r) {
|
||||
$now = time();
|
||||
while ($row = mysqli_fetch_assoc($r)) {
|
||||
$row['status'] = self::statusOf($row, $now);
|
||||
$out[] = $row;
|
||||
}
|
||||
mysqli_free_result($r);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function statusOf($row, $now)
|
||||
{
|
||||
if ((int) $row['active'] !== 1) {
|
||||
return 'Disabled';
|
||||
}
|
||||
if ((int) $row['expires'] > 0 && $now > (int) $row['expires']) {
|
||||
return 'Expired';
|
||||
}
|
||||
if ((int) $row['max_uses'] > 0 && (int) $row['uses'] >= (int) $row['max_uses']) {
|
||||
return 'Used up';
|
||||
}
|
||||
return 'Active';
|
||||
}
|
||||
|
||||
public static function setActive($id, $active)
|
||||
{
|
||||
$link = self::link();
|
||||
if (!$link) {
|
||||
return false;
|
||||
}
|
||||
self::ensureSchema();
|
||||
$stmt = mysqli_prepare($link,
|
||||
"UPDATE `" . TB_PREFIX . "gold_promo` SET active = ? WHERE id = ?");
|
||||
if (!$stmt) {
|
||||
return false;
|
||||
}
|
||||
$a = $active ? 1 : 0;
|
||||
$id = (int) $id;
|
||||
mysqli_stmt_bind_param($stmt, 'ii', $a, $id);
|
||||
$ok = mysqli_stmt_execute($stmt);
|
||||
mysqli_stmt_close($stmt);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
public static function deleteCode($id)
|
||||
{
|
||||
$link = self::link();
|
||||
if (!$link) {
|
||||
return false;
|
||||
}
|
||||
self::ensureSchema();
|
||||
$id = (int) $id;
|
||||
$stmt = mysqli_prepare($link, "DELETE FROM `" . TB_PREFIX . "gold_promo` WHERE id = ?");
|
||||
if ($stmt) {
|
||||
mysqli_stmt_bind_param($stmt, 'i', $id);
|
||||
mysqli_stmt_execute($stmt);
|
||||
mysqli_stmt_close($stmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Recent redemptions (joined with code + username) for the admin view. */
|
||||
public static function recentRedemptions($limit = 30)
|
||||
{
|
||||
$link = self::link();
|
||||
$out = [];
|
||||
if (!$link) {
|
||||
return $out;
|
||||
}
|
||||
self::ensureSchema();
|
||||
$limit = max(1, min(200, (int) $limit));
|
||||
$r = @mysqli_query($link,
|
||||
"SELECT rr.time, rr.gold, rr.uid, u.username, p.code
|
||||
FROM `" . TB_PREFIX . "gold_promo_redeem` rr
|
||||
LEFT JOIN `" . TB_PREFIX . "users` u ON u.id = rr.uid
|
||||
LEFT JOIN `" . TB_PREFIX . "gold_promo` p ON p.id = rr.promo_id
|
||||
ORDER BY rr.id DESC LIMIT " . $limit);
|
||||
if ($r) {
|
||||
while ($row = mysqli_fetch_assoc($r)) {
|
||||
$out[] = $row;
|
||||
}
|
||||
mysqli_free_result($r);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/* ---- Player redemption --------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Redeem a code for a player.
|
||||
* @return array [ok(bool), message(string), gold(int)]
|
||||
*/
|
||||
public static function redeem($uid, $code)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
if ($uid <= 0) {
|
||||
return [false, 'You must be logged in.', 0];
|
||||
}
|
||||
$link = self::link();
|
||||
if (!$link) {
|
||||
return [false, 'Service unavailable, try again later.', 0];
|
||||
}
|
||||
self::ensureSchema();
|
||||
|
||||
$code = self::normCode($code);
|
||||
if ($code === '') {
|
||||
return [false, 'Please enter a code.', 0];
|
||||
}
|
||||
|
||||
// Fetch the code row.
|
||||
$stmt = mysqli_prepare($link,
|
||||
"SELECT * FROM `" . TB_PREFIX . "gold_promo` WHERE code = ? LIMIT 1");
|
||||
if (!$stmt) {
|
||||
return [false, 'Service unavailable, try again later.', 0];
|
||||
}
|
||||
mysqli_stmt_bind_param($stmt, 's', $code);
|
||||
mysqli_stmt_execute($stmt);
|
||||
$res = mysqli_stmt_get_result($stmt);
|
||||
$promo = $res ? mysqli_fetch_assoc($res) : null;
|
||||
mysqli_stmt_close($stmt);
|
||||
|
||||
if (!$promo) {
|
||||
return [false, 'Invalid code.', 0];
|
||||
}
|
||||
$now = time();
|
||||
if ((int) $promo['active'] !== 1) {
|
||||
return [false, 'This code is no longer available.', 0];
|
||||
}
|
||||
if ((int) $promo['expires'] > 0 && $now > (int) $promo['expires']) {
|
||||
return [false, 'This code has expired.', 0];
|
||||
}
|
||||
if ((int) $promo['max_uses'] > 0 && (int) $promo['uses'] >= (int) $promo['max_uses']) {
|
||||
return [false, 'This code has already been fully redeemed.', 0];
|
||||
}
|
||||
|
||||
$promoId = (int) $promo['id'];
|
||||
$gold = (int) $promo['gold'];
|
||||
|
||||
// Per-user-once check.
|
||||
if ((int) $promo['per_user'] === 1) {
|
||||
$chk = mysqli_prepare($link,
|
||||
"SELECT 1 FROM `" . TB_PREFIX . "gold_promo_redeem` WHERE promo_id = ? AND uid = ? LIMIT 1");
|
||||
if ($chk) {
|
||||
mysqli_stmt_bind_param($chk, 'ii', $promoId, $uid);
|
||||
mysqli_stmt_execute($chk);
|
||||
mysqli_stmt_store_result($chk);
|
||||
$already = mysqli_stmt_num_rows($chk) > 0;
|
||||
mysqli_stmt_close($chk);
|
||||
if ($already) {
|
||||
return [false, 'You have already redeemed this code.', 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atomically claim a use (guards against concurrent over-redemption).
|
||||
$claim = mysqli_prepare($link,
|
||||
"UPDATE `" . TB_PREFIX . "gold_promo`
|
||||
SET uses = uses + 1
|
||||
WHERE id = ? AND active = 1
|
||||
AND (max_uses = 0 OR uses < max_uses)
|
||||
AND (expires = 0 OR expires >= ?)");
|
||||
if (!$claim) {
|
||||
return [false, 'Service unavailable, try again later.', 0];
|
||||
}
|
||||
mysqli_stmt_bind_param($claim, 'ii', $promoId, $now);
|
||||
mysqli_stmt_execute($claim);
|
||||
$claimed = mysqli_stmt_affected_rows($claim) === 1;
|
||||
mysqli_stmt_close($claim);
|
||||
|
||||
if (!$claimed) {
|
||||
return [false, 'This code has already been fully redeemed.', 0];
|
||||
}
|
||||
|
||||
// Grant the gold. Prefer the Database helper (also writes gold_fin_log).
|
||||
$granted = false;
|
||||
if (isset($GLOBALS['database']) && method_exists($GLOBALS['database'], 'modifyGold')) {
|
||||
$GLOBALS['database']->modifyGold($uid, $gold, 1);
|
||||
$granted = true;
|
||||
}
|
||||
if (!$granted) {
|
||||
$g = mysqli_prepare($link,
|
||||
"UPDATE `" . TB_PREFIX . "users` SET gold = gold + ? WHERE id = ?");
|
||||
if ($g) {
|
||||
mysqli_stmt_bind_param($g, 'ii', $gold, $uid);
|
||||
mysqli_stmt_execute($g);
|
||||
mysqli_stmt_close($g);
|
||||
}
|
||||
}
|
||||
|
||||
// Record the redemption.
|
||||
$ins = mysqli_prepare($link,
|
||||
"INSERT INTO `" . TB_PREFIX . "gold_promo_redeem` (promo_id, uid, gold, time)
|
||||
VALUES (?,?,?,?)");
|
||||
if ($ins) {
|
||||
mysqli_stmt_bind_param($ins, 'iiii', $promoId, $uid, $gold, $now);
|
||||
mysqli_stmt_execute($ins);
|
||||
mysqli_stmt_close($ins);
|
||||
}
|
||||
|
||||
return [true, 'Success! ' . $gold . ' gold has been added to your account.', $gold];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user