From a0ef06a35f5e1e929655253a7dd61858000c1084 Mon Sep 17 00:00:00 2001 From: levi Date: Sat, 14 Mar 2026 16:50:27 -0300 Subject: [PATCH] chore: harden php8 compatibility, optimize installer/croppers, and refresh docs/admin ux --- .env.example | 19 +- Admin/Templates/map_tile.tpl | 9 +- Admin/Templates/msg.tpl | 8 +- Admin/Templates/report.tpl | 14 +- Admin/Templates/resetServer.php | 10 +- Admin/Templates/users.tpl | 98 ++++++++ Admin/admin.php | 5 + CHANGELOG.md | 73 ++++++ DOCKER_README.md | 61 +++-- Dockerfile | 16 +- GameEngine/Automation.php | 5 +- GameEngine/Battle.php | 22 +- GameEngine/Building.php | 12 +- GameEngine/Database.php | 389 +++++++++++++++++++++----------- GameEngine/Lang/en.php | 12 +- GameEngine/Technology.php | 18 +- GameEngine/Units.php | 18 +- README.md | 201 +++++++++++------ Security/Security.class.php | 8 +- Templates/Plus/3.tpl | 104 ++------- Templates/Plus/pmenu.tpl | 2 +- build_croppers.php | 41 +++- docker-compose.yml | 17 +- install/ajax_croppers.php | 5 +- install/index.php | 9 +- install/templates/config.tpl | 71 +++++- mailme.php | 30 +-- massmessage.php | 5 + sysmsg.php | 7 + 29 files changed, 871 insertions(+), 418 deletions(-) create mode 100644 Admin/Templates/users.tpl create mode 100644 CHANGELOG.md diff --git a/.env.example b/.env.example index deebbeb7..6ca35268 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,18 @@ -# MySQL Database Configuration -MYSQL_ROOT_PASSWORD=rootpassword -MYSQL_DATABASE=travian -MYSQL_USER=travianz -MYSQL_PASSWORD=travianzpass +# MariaDB Database Configuration (preferred) +MARIADB_ROOT_PASSWORD=rootpassword +MARIADB_DATABASE=travian +MARIADB_USER=travianz +MARIADB_PASSWORD=travianzpass + +# Legacy compatibility keys (optional) +# These keys inherit directly from MARIADB_* to avoid duplicate values. +MYSQL_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD} +MYSQL_DATABASE=${MARIADB_DATABASE} +MYSQL_USER=${MARIADB_USER} +MYSQL_PASSWORD=${MARIADB_PASSWORD} # Application Configuration -# These values will be used during the installation wizard +# These values will be used during the installation wizard. # Hostname for database connection (use 'db' when running in Docker) DB_HOST=db DB_PORT=3306 diff --git a/Admin/Templates/map_tile.tpl b/Admin/Templates/map_tile.tpl index 8d7d31a2..11010ee2 100644 --- a/Admin/Templates/map_tile.tpl +++ b/Admin/Templates/map_tile.tpl @@ -13,6 +13,7 @@ ################################################################################# function get_map_tile_info($coord_x, $coord_y){ // todo mv to queries file + $tbp = TB_PREFIX; $q = 'SELECT map_data.`id` AS village_id, map_data.`fieldtype`, map_data.`oasistype`, map_data.`occupied`, map_data.`image`, '. 'oasis_data.`type`, '. 'CASE '. @@ -21,10 +22,10 @@ function get_map_tile_info($coord_x, $coord_y){ // todo mv to queries file 'ELSE village_data.`owner` '. 'END AS owner_id, '. 'u.`username` '. - 'FROM (SELECT * FROM `s1_wdata` WHERE `x` = '.$coord_x.' AND `y` = '.$coord_y.') AS map_data '. - 'LEFT JOIN `s1_odata` AS oasis_data ON map_data.`id` = oasis_data.`wref` '. - 'LEFT JOIN `s1_vdata` AS village_data ON village_data.`wref` = map_data.`id` '. - 'LEFT JOIN s1_users AS u ON u.`id` = COALESCE(oasis_data.`owner`, village_data.`owner`, 0);'; // todo check this db tables fields -- del doublings like oasistype = '2' and image = 'o2' + 'FROM (SELECT * FROM `'.$tbp.'wdata` WHERE `x` = '.$coord_x.' AND `y` = '.$coord_y.') AS map_data '. + 'LEFT JOIN `'.$tbp.'odata` AS oasis_data ON map_data.`id` = oasis_data.`wref` '. + 'LEFT JOIN `'.$tbp.'vdata` AS village_data ON village_data.`wref` = map_data.`id` '. + 'LEFT JOIN `'.$tbp.'users` AS u ON u.`id` = COALESCE(oasis_data.`owner`, village_data.`owner`, 0);'; // todo check this db tables fields -- del doublings like oasistype = '2' and image = 'o2' $result = mysqli_query($GLOBALS['link'], $q); return mysqli_fetch_assoc($result); } diff --git a/Admin/Templates/msg.tpl b/Admin/Templates/msg.tpl index 36835524..af17bf98 100644 --- a/Admin/Templates/msg.tpl +++ b/Admin/Templates/msg.tpl @@ -12,7 +12,11 @@ include_once("../GameEngine/Generator.php"); include_once("../GameEngine/Technology.php"); include_once("../GameEngine/Message.php"); -if(isset($_GET['nid']) && is_numeric($_GET['nid'])) $msg = $database->getMessage($_GET['nid'], 3); +$msg = []; +$allMessages = []; +$nid = (isset($_GET['nid']) && is_numeric($_GET['nid'])) ? (int) $_GET['nid'] : 0; + +if($nid > 0) $msg = $database->getMessage($nid, 3); else { $sql = "SELECT * FROM " . TB_PREFIX . "mdata ORDER BY time DESC "; @@ -75,5 +79,5 @@ echo stripslashes(nl2br($bbcoded));
\ No newline at end of file diff --git a/Admin/Templates/report.tpl b/Admin/Templates/report.tpl index 48b8dfce..ce2f3ee1 100644 --- a/Admin/Templates/report.tpl +++ b/Admin/Templates/report.tpl @@ -12,7 +12,13 @@ include_once("../GameEngine/Generator.php"); include_once("../GameEngine/Technology.php"); include_once("../GameEngine/Message.php"); -if ($_GET['bid']) $rep = $database->getNotice2($_GET['bid']); +$rep = null; +$rep1 = []; +$bid = isset($_GET['bid']) ? (int) $_GET['bid'] : 0; + +if ($bid > 0) { + $rep = $database->getNotice2($bid); +} else { $sql = "SELECT * FROM " . TB_PREFIX . "ndata ORDER BY time DESC "; @@ -20,7 +26,7 @@ else $rep1 = $database->mysqli_fetch_all($result); } -if($rep1) +if(!empty($rep1)) { ?> @@ -34,7 +40,7 @@ if($rep1) @@ -50,5 +56,5 @@ elseif($rep) $message->readingNotice = $rep; include ("../Templates/Notice/".$message->getReportType($rep['ntype']).".tpl"); } -else echo "Report ID ".$_GET['bid']." doesn't exist!"; +else echo "Report ID ".$bid." doesn't exist!"; ?> diff --git a/Admin/Templates/resetServer.php b/Admin/Templates/resetServer.php index 20af88ea..edacca2c 100644 --- a/Admin/Templates/resetServer.php +++ b/Admin/Templates/resetServer.php @@ -1,4 +1,4 @@ - Access Denied: You are not Admin!"); } -if($_SESSION['access'] != ADMIN) die("

Access Denied: You are not Admin!

"); set_time_limit(0); // TODO: truncate ALL tables (in a single query, not like this), // then perform install steps (createDbStructure() && populateWorldData()) diff --git a/Admin/Templates/users.tpl b/Admin/Templates/users.tpl new file mode 100644 index 00000000..56841873 --- /dev/null +++ b/Admin/Templates/users.tpl @@ -0,0 +1,98 @@ + 0]; +$totalUsers = (int)($totalRow['cnt'] ?? 0); +$totalPages = max(1, (int)ceil($totalUsers / $perPage)); + +if ($page > $totalPages) { + $page = $totalPages; + $offset = ($page - 1) * $perPage; +} + +$sql = "SELECT id, username, email, access, tribe, gold, timestamp " . + "FROM " . TB_PREFIX . "users " . + "ORDER BY id DESC LIMIT " . (int)$perPage . " OFFSET " . (int)$offset; +$result = mysqli_query($GLOBALS['link'], $sql); + +function tribeLabel($tribe) { + $tribe = (int)$tribe; + if ($tribe === 1) return 'Roman'; + if ($tribe === 2) return 'Teuton'; + if ($tribe === 3) return 'Gaul'; + return 'N/A'; +} +?> + + + + + + + + + + + + + + + + + + 0) { ?> + + + + + + + + + + + + + + + + + +
Users list ()
IDUsernameEmailAccessTribeGoldLast activity
+ + + + + + + + + + - + +
No users found.
+ +
+ 1) { ?> + « Previous + + Page / + + Next » + +
diff --git a/Admin/admin.php b/Admin/admin.php index 77c03fed..2623bc9b 100644 --- a/Admin/admin.php +++ b/Admin/admin.php @@ -131,6 +131,10 @@ if (!empty($_GET['p'])) { $subpage = 'Create Users'; break; + case 'users': + $subpage = 'Users List'; + break; + case 'admin_log': $subpage = 'Admin Log'; break; @@ -559,6 +563,7 @@ if (!empty($_GET['p'])) {
  • Users
  • diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..33378419 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +All notable changes to this project are documented in this file. + +Format inspired by Keep a Changelog and Semantic Versioning principles. + +## [Unreleased] + +## [2026-03-14] - Stability, PHP 8, Docker and Installer Hardening + +### Added +- New root changelog file. +- Admin panel option to list users in the Users menu (`?p=users`). +- New admin users list page with pagination and links to player details: `Admin/Templates/users.tpl`. +- Installer SQL defaults in config form now read from `.env` (with fallback to legacy `MYSQL_*` keys). +- Random database prefix generation per installation session. +- Installer timezone option for Brazil (`America/Sao_Paulo`) and default timezone set to Sao Paulo. +- SSE explicit error payload in croppers builder to avoid infinite reconnect loops. + +### Changed +- Docker runtime upgraded to PHP 8.3 (Apache). +- Docker Compose database moved to MariaDB latest. +- Compose configuration modernized (v2 style), env naming standardized to `MARIADB_*` with compatibility support. +- README and Docker docs updated to reflect PHP 8/MariaDB and modern compose commands. +- Installer timezone id mapping fixed in `install/index.php` to match form options. +- Croppers build progress behavior adjusted to report incremental progress in batches. + +### Fixed +- PHP 8 compatibility: + - Replaced deprecated `each()` usage. + - Added safe guards for `magic_quotes_*` checks. +- Multiple warnings/fatals from undefined variables and null array offsets across game/admin/installer pages. +- Duplicate constant definition warnings in language file (preventing header issues). +- Duplicate key failures for croppers index creation in PHP 8 (`mysqli_sql_exception` behavior) by making index ops idempotent. +- Installer and runtime DB connection robustness: + - host:port normalization, + - retry logic, + - throwable-safe handling. +- Duplicate primary key errors in units insertion by making `addUnits` idempotent (`ON DUPLICATE KEY UPDATE`). +- Admin template warnings in report/message pages (undefined keys/indexes). +- Admin map tile query now uses dynamic table prefix (`TB_PREFIX`) instead of hardcoded `s1_` tables. +- Session/header issues in admin reset script: + - removed output before ` backup_$(date +%Y%m%d).sql +docker exec travianz-db mariadb-dump -u root -p travian > backup_$(date +%Y%m%d).sql ``` ### Restore Database ```bash -docker exec -i travianz-db mysql -u root -p travian < backup_20231125.sql +docker exec -i travianz-db mariadb -u root -p travian < backup_20231125.sql ``` ### Backup Application Files @@ -237,7 +237,7 @@ For production environments, consider the following: 2. **Use SSL/TLS:** Set up a reverse proxy (nginx/traefik) with Let's Encrypt -3. **Limit Database Access:** Remove the database port exposure in `docker-compose.yml` +3. **Limit Database Access:** Remove the database port exposure in the `docker compose` configuration (`docker-compose.yml`) 4. **Regular Backups:** Set up automated backup scripts @@ -257,15 +257,14 @@ services: ## Performance Optimization -### MySQL Tuning +### MariaDB Tuning -Edit `docker-compose.yml` to add MySQL configuration: +Edit `docker-compose.yml` to add MariaDB configuration: ```yaml services: db: command: > - --default-authentication-plugin=mysql_native_password --sql_mode="" --max_connections=200 --innodb_buffer_pool_size=512M @@ -299,8 +298,8 @@ To update TravianZ to the latest version: ```bash git pull origin main -docker-compose down -docker-compose up -d --build +docker compose down +docker compose up -d --build ``` ## Support diff --git a/Dockerfile b/Dockerfile index 8c0959ff..f81c404a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM php:7.4-apache +FROM php:8.3-apache # Install system dependencies RUN apt-get update && apt-get install -y \ libpng-dev \ - libjpeg-dev \ + libjpeg62-turbo-dev \ libfreetype6-dev \ libzip-dev \ zip \ @@ -26,18 +26,14 @@ RUN a2enmod rewrite headers # Set working directory WORKDIR /var/www/html -# Copy application files -COPY . /var/www/html/ - -# Set permissions -RUN chown -R www-data:www-data /var/www/html \ - && chmod -R 755 /var/www/html \ - && chmod -R 777 /var/www/html/var +# Runtime source code is bind-mounted by docker-compose. +# Keep only minimal folder bootstrap inside the image. +RUN mkdir -p /var/www/html/var # Configure Apache to use /var/www/html as DocumentRoot RUN sed -i 's!/var/www/html!/var/www/html!g' /etc/apache2/sites-available/000-default.conf -# Expose port 80 +# Expose Apache port EXPOSE 80 # Start Apache diff --git a/GameEngine/Automation.php b/GameEngine/Automation.php index 56c9a4c8..a7221fbf 100644 --- a/GameEngine/Automation.php +++ b/GameEngine/Automation.php @@ -1192,7 +1192,7 @@ class Automation { if($traps >= $totalTroops){ for($i = 1; $i <= 11; $i++) ${'traped'.$i} = $data['t'.$i]; } - else + else if($totalTroops > 0) { $multiplier = $traps / $totalTroops; @@ -1213,6 +1213,9 @@ class Automation { } } } + else { + for($i = 1; $i <= 11; $i++) ${'traped'.$i} = 0; + } } if(empty($scout) || $NatarCapital){ diff --git a/GameEngine/Battle.php b/GameEngine/Battle.php index dc98b105..8b09d7e0 100644 --- a/GameEngine/Battle.php +++ b/GameEngine/Battle.php @@ -477,6 +477,8 @@ class Battle { $result['Attack_points'] = $rap; $result['Defend_points'] = $rdp; $winner = ($rap > $rdp); + $safeRap = max(1, (float) $rap); + $safeRdp = max(1, (float) $rdp); // Formula for calculating the Morale bonus // WW villages aren't affected by this bonus @@ -495,7 +497,7 @@ class Battle { // $type = 1 Scout, 2 Enforcement // $type = 3 Normal, 4 Raid if($type == 1){ - $holder = pow((($rdp * $moralbonus) / $rap), $Mfactor); + $holder = pow((($rdp * $moralbonus) / $safeRap), $Mfactor); if($holder > 1) $holder = 1; if ($rdp > $rap) $holder = 1; @@ -507,7 +509,7 @@ class Battle { //Defender result $result[2] = 0; }else if($type == 4) { - $holder = ($winner) ? pow((($rdp * $moralbonus) / $rap), $Mfactor) : pow(($rap / ($rdp * $moralbonus)), $Mfactor); + $holder = ($winner) ? pow((($rdp * $moralbonus) / $safeRap), $Mfactor) : pow(($safeRap / max($safeRdp * $moralbonus, 1)), $Mfactor); $holder = $holder / (1 + $holder); //Attacker result $result[1] = $winner ? $holder : 1 - $holder; @@ -518,7 +520,7 @@ class Battle { }else if($type == 3){ // Attacker - $result[1] = ($winner) ? pow((($rdp * $moralbonus) / $rap), $Mfactor) : 1; + $result[1] = ($winner) ? pow((($rdp * $moralbonus) / $safeRap), $Mfactor) : 1; if ($result[1] > 1){ $result[1] = 1; @@ -527,9 +529,9 @@ class Battle { } // Defender - $result[2] = (!$winner) ? pow(($rap / ($rdp * $moralbonus)), $Mfactor) : 1; + $result[2] = (!$winner) ? pow(($safeRap / max($safeRdp * $moralbonus, 1)), $Mfactor) : 1; - if ($result[1] == 1) $result[2] = pow(($rap / ($rdp * $moralbonus)), $Mfactor); + if ($result[1] == 1) $result[2] = pow(($safeRap / max($safeRdp * $moralbonus, 1)), $Mfactor); if ($result[2] > 1) { $result[2] = 1; @@ -569,14 +571,14 @@ class Battle { $catpMoraleBonus = min(max(($attpop / ($defpop > 0 ? $defpop : 1)) ** 0.3, 1), 3); //New level of the building (only for warsim.php) - $catapultsDamage = $this->calculateCatapultsDamage($catp, $upgrades, $durability, $rap / $rdp, $strongerbuildings, $catpMoraleBonus); + $catapultsDamage = $this->calculateCatapultsDamage($catp, $upgrades, $durability, $safeRap / $safeRdp, $strongerbuildings, $catpMoraleBonus); $result[3] = $this->calculateNewBuildingLevel($tblevel, $catapultsDamage); $result[4] = $tblevel; //Results for Automation.php $result['catapults']['upgrades'] = $upgrades; $result['catapults']['durability'] = $durability; - $result['catapults']['attackDefenseRatio'] = $rap / $rdp; + $result['catapults']['attackDefenseRatio'] = $safeRap / $safeRdp; $result['catapults']['strongerBuildings'] = $strongerbuildings; $result['catapults']['moraleBonus'] = $catpMoraleBonus; } @@ -590,19 +592,19 @@ class Battle { $durability = ($stonemason > 0 ? $bid34[$stonemason]['attri'] / 100 : 1); // New level of the building (only for warsim.php) - $ramsDamage = $this->calculateCatapultsDamage($ram, $upgrades, $durability, $rap / $rdp, $strongerbuildings, 1); + $ramsDamage = $this->calculateCatapultsDamage($ram, $upgrades, $durability, $safeRap / $safeRdp, $strongerbuildings, 1); $result[7] = $this->calculateNewBuildingLevel($walllevel, $ramsDamage); $result[8] = $walllevel; // Results for Automation.php $result['rams']['upgrades'] = $upgrades; $result['rams']['durability'] = $durability; - $result['rams']['attackDefenseRatio'] = $rap / $rdp; + $result['rams']['attackDefenseRatio'] = $safeRap / $safeRdp; $result['rams']['strongerBuildings'] = $strongerbuildings; $result['rams']['moraleBonus'] = 1; } - $result[6] = pow($rap / ($rdp * $moralbonus > 0 ? $rdp * $moralbonus : 1), $Mfactor); + $result[6] = pow($safeRap / max($safeRdp * $moralbonus, 1), $Mfactor); $result['moralBonus'] = $moralbonus; $total_att_units = count($units['Att_unit']); diff --git a/GameEngine/Building.php b/GameEngine/Building.php index f745d153..ce9e7305 100755 --- a/GameEngine/Building.php +++ b/GameEngine/Building.php @@ -865,10 +865,14 @@ class Building { $rclay = $uprequire['clay'] - $village->aclay; $rcrop = $uprequire['crop'] - $village->acrop; $riron = $uprequire['iron'] - $village->airon; - $rwtime = $rwood / $village->getProd("wood") * 3600; - $rcltime = $rclay / $village->getProd("clay") * 3600; - $rctime = $rcrop / $village->getProd("crop") * 3600; - $ritime = $riron / $village->getProd("iron") * 3600; + $woodProd = $village->getProd("wood"); + $clayProd = $village->getProd("clay"); + $cropProd = $village->getProd("crop"); + $ironProd = $village->getProd("iron"); + $rwtime = ($rwood <= 0) ? 0 : ($woodProd > 0 ? $rwood / $woodProd * 3600 : 0); + $rcltime = ($rclay <= 0) ? 0 : ($clayProd > 0 ? $rclay / $clayProd * 3600 : 0); + $rctime = ($rcrop <= 0) ? 0 : ($cropProd > 0 ? $rcrop / $cropProd * 3600 : 0); + $ritime = ($riron <= 0) ? 0 : ($ironProd > 0 ? $riron / $ironProd * 3600 : 0); $reqtime = max($rwtime, $rctime, $rcltime, $ritime); $reqtime += time(); return $generator->procMtime($reqtime); diff --git a/GameEngine/Database.php b/GameEngine/Database.php index 15a9af43..f1dde1dd 100755 --- a/GameEngine/Database.php +++ b/GameEngine/Database.php @@ -434,28 +434,40 @@ class MYSQLi_DB implements IDbConnection { * @see \App\Database\IDbConnection::connect() */ public function connect() { - // try to connect - try { - $this->dblink = mysqli_connect( $this->hostname, $this->username, $this->password, $this->dbname, $this->port ); - } catch (\Exception $exception) { - $this->dblink = mysqli_connect( $this->hostname . ':' . $this->port, $this->username, $this->password ); + $host = (string) $this->hostname; + $port = (int) $this->port; + if ($port <= 0) $port = 3306; - // return on error - if (mysqli_error($this->dblink)) { - return false; + // If host is in form host:port (common in env files), split it once. + if (strpos($host, ':') !== false && substr_count($host, ':') === 1) { + $hostPort = explode(':', $host, 2); + if (isset($hostPort[0], $hostPort[1]) && is_numeric($hostPort[1])) { + $host = $hostPort[0]; + $port = (int) $hostPort[1]; } - - // select the DB to use - mysqli_select_db($this->dblink, $this->dbname); } - // return on error - if (mysqli_error($this->dblink)) { - return false; - } else { - // connected and DB exists, we're good to go - return true; - } + $attempts = 15; + $waitMicros = 1000000; // 1 second + + for ($i = 0; $i < $attempts; $i++) { + try { + $this->dblink = mysqli_connect($host, $this->username, $this->password, $this->dbname, $port); + } catch (\Throwable $exception) { + $this->dblink = false; + } + + if ($this->dblink instanceof \mysqli) { + return true; + } + + // During container startup DB may still be booting. + if ($i < $attempts - 1) { + usleep($waitMicros); + } + } + + return false; } /** @@ -1162,7 +1174,7 @@ public function getBestOasisCropBonus($x, $y) { $q = "SELECT timestamp from " . TB_PREFIX . "deleting where uid = $uid LIMIT 1"; $result = mysqli_query($this->dblink,$q); $dbarray = mysqli_fetch_array($result); - return $dbarray['timestamp']; + return isset($dbarray['timestamp']) ? (int)$dbarray['timestamp'] : 0; } function modifyGold($userid, $amt, $mode) { @@ -1728,10 +1740,10 @@ public function getBestOasisCropBonus($x, $y) { function getVillageState($wref, $use_cache = true) { // retrieve this value from the full village data cache if ($use_cache && ($cachedValue = self::returnCachedContent(self::$villageFieldsCacheByWorldID, $wref)) && !is_null($cachedValue)) { - return ($cachedValue['occupied'] != 0 || $cachedValue['oasistype'] != 0); + return (((int)($cachedValue['occupied'] ?? 0)) != 0 || ((int)($cachedValue['oasistype'] ?? 0)) != 0); } else { $vil = $this->getVillageByWorldID($wref, $use_cache); - return ($vil['occupied'] != 0 || $vil['oasistype'] != 0); + return (((int)($vil['occupied'] ?? 0)) != 0 || ((int)($vil['oasistype'] ?? 0)) != 0); } } @@ -2075,7 +2087,7 @@ public function getBestOasisCropBonus($x, $y) { // return a single value if (!$array_passed) { - self::$villageFieldsCacheByWorldID[$vid[0]] = $result[0]; + self::$villageFieldsCacheByWorldID[$vid[0]] = (isset($result[0]) && is_array($result[0])) ? $result[0] : []; } else { if ($result && count($result)) { foreach ( $result as $record ) { @@ -3715,7 +3727,7 @@ public function getBestOasisCropBonus($x, $y) { 'LIMIT 1'); $row = mysqli_fetch_array($result, MYSQLI_ASSOC); - self::$fieldLevelsInVillageSearchCache[$vid.$fieldType] = $row['level']; + self::$fieldLevelsInVillageSearchCache[$vid.$fieldType] = isset($row['level']) ? (int)$row['level'] : 0; return self::$fieldLevelsInVillageSearchCache[$vid.$fieldType]; } @@ -3730,9 +3742,9 @@ public function getBestOasisCropBonus($x, $y) { $q = "SELECT f" . $field . " from " . TB_PREFIX . "fdata where vref = $vid LIMIT 1"; $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result); + $row = $result ? mysqli_fetch_array($result, MYSQLI_ASSOC) : null; - self::$resourceLevelsCache[$vid.$field] = $row["f" . $field]; + self::$resourceLevelsCache[$vid.$field] = (is_array($row) && array_key_exists("f".$field, $row)) ? (int)$row["f" . $field] : 0; return self::$resourceLevelsCache[$vid.$field]; } @@ -5874,22 +5886,36 @@ References: User ID/Message ID, Mode */ function addUnits($vid, $troopsArray = null) { - list($vid, $type, $values) = $this->escape_input($vid, $type, $values); + list($vid) = $this->escape_input($vid); if(empty($vid)) return; if (!is_array($vid)) $vid = [$vid]; - $types = $values = ""; + $types = ""; + $typeKeys = []; + $values = []; if($troopsArray != null){ - $types = $troopsArray[0]; + $typeKeys = $troopsArray[0]; $values = $troopsArray[1]; - $types = ",u".implode(",u", $types); + $types = ",u".implode(",u", $typeKeys); } - foreach ($vid as $index => $vidValue) $vid[$index] = (int) $vidValue.($troopsArray != null ? ",".implode(",", $values[$index]) : ""); + foreach ($vid as $index => $vidValue) $vid[$index] = (int) $vidValue.($troopsArray != null ? ",".implode(",", $values[$index]) : ""); - $q = "INSERT into " . TB_PREFIX . "units (vref$types) values (".implode('),(', $vid).")"; + $duplicateUpdate = ""; + if(!empty($typeKeys)){ + $duplicateColumns = []; + foreach($typeKeys as $typeKey){ + $typeKey = (int) $typeKey; + $duplicateColumns[] = "u".$typeKey."=VALUES(u".$typeKey.")"; + } + $duplicateUpdate = " ON DUPLICATE KEY UPDATE ".implode(', ', $duplicateColumns); + }else{ + $duplicateUpdate = " ON DUPLICATE KEY UPDATE vref=vref"; + } + + $q = "INSERT into " . TB_PREFIX . "units (vref$types) values (".implode('),(', $vid).")".$duplicateUpdate; return mysqli_query($this->dblink,$q); } @@ -6965,13 +6991,47 @@ References: User ID/Message ID, Mode public function populateWorldData() { global $autoprefix; + $wdataTable = TB_PREFIX . "wdata"; + $droppedIndexes = []; + try { // check if we don't already have world data - $data_exist = $this->query_return("SELECT * FROM " . TB_PREFIX . "wdata LIMIT 1"); + $data_exist = $this->query_return("SELECT * FROM " . $wdataTable . " LIMIT 1"); if ($data_exist && count($data_exist)) { return false; } + // Best-effort session tuning for faster bulk inserts. + $sessionTuningStatements = [ + "SET SESSION innodb_flush_log_at_trx_commit=2", + "SET SESSION sync_binlog=0", + "SET SESSION unique_checks=0", + "SET SESSION foreign_key_checks=0", + ]; + foreach ($sessionTuningStatements as $stmt) { + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore tuning failures, they are not required for correctness. + } + } + + // Temporarily drop secondary indexes before heavy INSERT .. SELECT. + // Recreated at the end to speed up map generation on larger worlds. + $indexesToDrop = ['occupied', 'fieldtype', 'x-y']; + foreach ($indexesToDrop as $indexName) { + try { + $escapedIndexName = mysqli_real_escape_string($this->dblink, $indexName); + $res = mysqli_query($this->dblink, "SHOW INDEX FROM `{$wdataTable}` WHERE Key_name = '{$escapedIndexName}'"); + if ($res && mysqli_num_rows($res) > 0) { + mysqli_query($this->dblink, "ALTER TABLE `{$wdataTable}` DROP INDEX `{$indexName}`"); + $droppedIndexes[$indexName] = true; + } + } catch (Throwable $e) { + // If we can't drop an index, continue with generation anyway. + } + } + // load the data generation SQL file $str = file_get_contents($autoprefix."var/db/datagen-world-data.sql"); $str = preg_replace(["'%PREFIX%'", "'%WORLDSIZE%'"], [TB_PREFIX, WORLD_MAX], $str); @@ -6988,8 +7048,31 @@ References: User ID/Message ID, Mode if (!$result) { return -1; } - } catch (\Exception $e) { + } catch (\Throwable $e) { return -1; + } finally { + // Recreate dropped indexes to keep runtime query performance unchanged. + if (!empty($droppedIndexes)) { + try { + if (isset($droppedIndexes['occupied'])) { + mysqli_query($this->dblink, "CREATE INDEX `occupied` ON `{$wdataTable}` (`occupied`)"); + } + if (isset($droppedIndexes['fieldtype'])) { + mysqli_query($this->dblink, "CREATE INDEX `fieldtype` ON `{$wdataTable}` (`fieldtype`)"); + } + if (isset($droppedIndexes['x-y'])) { + mysqli_query($this->dblink, "CREATE INDEX `x-y` ON `{$wdataTable}` (`x`, `y`)"); + } + } catch (Throwable $e) { + // Best effort only; installation can proceed and indexes may be rebuilt manually if needed. + } + } + + // Restore conservative defaults for this session (best effort). + try { mysqli_query($this->dblink, "SET SESSION unique_checks=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION foreign_key_checks=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION innodb_flush_log_at_trx_commit=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION sync_binlog=1"); } catch (Throwable $e) {} } return true; @@ -7012,121 +7095,171 @@ References: User ID/Message ID, Mode } public function populateCroppers(int $countTotal = 0, bool $truncateFirst = false, int $batch = 20000, ?callable $reporter = null ): array { - - @set_time_limit(0); - @ini_set('memory_limit', '1G'); + @set_time_limit(0); + @ini_set('memory_limit', '1G'); - $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; - $CROP_TABLE = $TBP . 'croppers'; - $WDATA = $TBP . 'wdata'; + $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; + $CROP_TABLE = $TBP . 'croppers'; + $WDATA = $TBP . 'wdata'; - // Count once if caller didn't - if ($countTotal <= 0) { - $row = mysqli_fetch_assoc(mysqli_query($this->dblink, - "SELECT COUNT(*) cnt FROM `$WDATA` WHERE fieldtype IN (1,6)")); - $countTotal = (int)($row['cnt'] ?? 0); - } + $total = 0; + $inTransaction = false; - if ($truncateFirst) { - if (!mysqli_query($this->dblink, "TRUNCATE TABLE `$CROP_TABLE`")) { - return ['ok'=>false,'msg'=>'TRUNCATE failed: '.mysqli_error($this->dblink)]; - } - } + try { + if ($countTotal <= 0) { + $row = mysqli_fetch_assoc(mysqli_query($this->dblink, "SELECT COUNT(*) cnt FROM `$WDATA` WHERE fieldtype IN (1,6)")); + $countTotal = (int)($row['cnt'] ?? 0); + } - // Session-level speed knobs (local to this connection) - @mysqli_query($this->dblink, "SET innodb_flush_log_at_trx_commit=2"); - @mysqli_query($this->dblink, "SET sync_binlog=0"); - @mysqli_query($this->dblink, "SET unique_checks=0"); - @mysqli_query($this->dblink, "SET foreign_key_checks=0"); + if ($truncateFirst && !mysqli_query($this->dblink, "TRUNCATE TABLE `$CROP_TABLE`")) { + return ['ok'=>false,'msg'=>'TRUNCATE failed: '.mysqli_error($this->dblink)]; + } - // Read big windows; write in safe slices to avoid max_allowed_packet - if ($batch < 1000) $batch = 1000; - if ($batch > 100000) $batch = 100000; - if($countTotal < 1000) $sliceSize = 200; - elseif($countTotal < 5000) $sliceSize = 500; - elseif($countTotal > 5000) $sliceSize = 1000; + $sessionTuningStatements = [ + "SET SESSION innodb_flush_log_at_trx_commit=2", + "SET SESSION sync_binlog=0", + "SET SESSION unique_checks=0", + "SET SESSION foreign_key_checks=0", + ]; + foreach($sessionTuningStatements as $stmt){ + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore tuning failures, they are not required for correctness. + } + } - $total = 0; - $lastId = 0; + if ($batch < 1000) $batch = 1000; + if ($batch > 100000) $batch = 100000; + if($countTotal < 1000) $sliceSize = 200; + elseif($countTotal < 5000) $sliceSize = 500; + else $sliceSize = 1000; - // Cursor pagination (no OFFSET) - while (true) { - $res = mysqli_query( - $this->dblink, - "SELECT id AS wref, x, y, fieldtype - FROM `$WDATA` - WHERE fieldtype IN (1,6) AND id > $lastId - ORDER BY id ASC - LIMIT $batch" - ); - if (!$res) { - return ['ok'=>false,'msg'=>'SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; - } + $lastId = 0; + while (true) { + $res = mysqli_query( + $this->dblink, + "SELECT id AS wref, x, y, fieldtype + FROM `$WDATA` + WHERE fieldtype IN (1,6) AND id > $lastId + ORDER BY id ASC + LIMIT $batch" + ); + if (!$res) { + return ['ok'=>false,'msg'=>'SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } - $rows = []; - while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } - if (!$rows) break; + $rows = []; + while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } + if (!$rows) break; - mysqli_begin_transaction($this->dblink); + mysqli_begin_transaction($this->dblink); + $inTransaction = true; - $n = count($rows); - for ($i = 0; $i < $n; $i += $sliceSize) { - $chunk = array_slice($rows, $i, $sliceSize); - $values = []; + $n = count($rows); + for ($i = 0; $i < $n; $i += $sliceSize) { + $chunk = array_slice($rows, $i, $sliceSize); + $values = []; - foreach ($chunk as $r) { - $x = (int)$r['x']; - $y = (int)$r['y']; + // Compute oasis crop bonus in batch for the whole chunk. + $bonusByWref = []; + $wrefs = []; + foreach ($chunk as $r) { + $wrefs[] = (int)$r['wref']; + } - // Your existing helper: - $bonus = (int)$this->getBestOasisCropBonus($x, $y); - if ($bonus < 0) $bonus = 0; - if ($bonus > 150) $bonus = 150; + if (!empty($wrefs)) { + $bonusSql = "SELECT + c.id AS wref, + LEAST( + 150, + (50 * LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3)) + + (25 * LEAST( + GREATEST(3 - LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3), 0), + SUM(CASE WHEN od.type IN (4,9,10,11) THEN 1 ELSE 0 END) + )) + ) AS bonus + FROM `$WDATA` c + LEFT JOIN `$WDATA` o + ON o.fieldtype = 0 + AND o.x BETWEEN (c.x - 3) AND (c.x + 3) + AND o.y BETWEEN (c.y - 3) AND (c.y + 3) + LEFT JOIN `{$TBP}odata` od + ON od.wref = o.id + AND od.type IN (12,4,9,10,11) + WHERE c.id IN (".implode(',', $wrefs).") + GROUP BY c.id"; - $values[] = sprintf("(%d,%d,%d,%d,%d)", - (int)$r['wref'], $x, $y, (int)$r['fieldtype'], $bonus - ); - } + $bonusRes = mysqli_query($this->dblink, $bonusSql); + if (!$bonusRes) { + mysqli_rollback($this->dblink); + $inTransaction = false; + return ['ok'=>false,'msg'=>'BONUS SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } - if ($values) { - // ODKU is cheaper than REPLACE (no DELETE) - $sql = "INSERT INTO `$CROP_TABLE` - (`wref`,`x`,`y`,`fieldtype`,`best_oasis_bonus`) - VALUES ".implode(',', $values)." - ON DUPLICATE KEY UPDATE - `x`=VALUES(`x`), - `y`=VALUES(`y`), - `fieldtype`=VALUES(`fieldtype`), - `best_oasis_bonus`=VALUES(`best_oasis_bonus`)"; - if (!mysqli_query($this->dblink, $sql)) { - mysqli_rollback($this->dblink); - return ['ok'=>false,'msg'=>'INSERT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; - } - } + while ($bonusRow = mysqli_fetch_assoc($bonusRes)) { + $bonusByWref[(int)$bonusRow['wref']] = (int)($bonusRow['bonus'] ?? 0); + } + } - // progress after each slice - $total += count($chunk); - if ($reporter) { - $pct = $countTotal ? min(100, (int)floor(($total / $countTotal) * 100)) : 0; - $reporter($total, $countTotal, $pct); - } - } + foreach ($chunk as $r) { + $x = (int)$r['x']; + $y = (int)$r['y']; + $bonus = (int)($bonusByWref[(int)$r['wref']] ?? 0); + if ($bonus < 0) $bonus = 0; + if ($bonus > 150) $bonus = 150; + $values[] = sprintf("(%d,%d,%d,%d,%d)", (int)$r['wref'], $x, $y, (int)$r['fieldtype'], $bonus); + } - mysqli_commit($this->dblink); + if ($values) { + $sql = "INSERT INTO `$CROP_TABLE` + (`wref`,`x`,`y`,`fieldtype`,`best_oasis_bonus`) + VALUES ".implode(',', $values)." + ON DUPLICATE KEY UPDATE + `x`=VALUES(`x`), + `y`=VALUES(`y`), + `fieldtype`=VALUES(`fieldtype`), + `best_oasis_bonus`=VALUES(`best_oasis_bonus`)"; + if (!mysqli_query($this->dblink, $sql)) { + mysqli_rollback($this->dblink); + $inTransaction = false; + return ['ok'=>false,'msg'=>'INSERT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } + } - // advance cursor - $lastId = (int)$rows[$n - 1]['wref']; - } + $total += count($chunk); + if ($reporter) { + $pct = $countTotal ? min(100, (int)floor(($total / $countTotal) * 100)) : 0; + $reporter($total, $countTotal, $pct); + } + } - // Restore checks (optional) - @mysqli_query($this->dblink, "SET unique_checks=1"); - @mysqli_query($this->dblink, "SET foreign_key_checks=1"); + mysqli_commit($this->dblink); + $inTransaction = false; + $lastId = (int)$rows[$n - 1]['wref']; + } - // Analyze once at the end - @mysqli_query($this->dblink, "ANALYZE TABLE `$CROP_TABLE`"); + foreach(["SET SESSION unique_checks=1", "SET SESSION foreign_key_checks=1"] as $stmt){ + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore restore failures if the engine does not support the setting. + } + } - if ($reporter) { $reporter($total, $countTotal, 100); } - return ['ok'=>true,'msg'=>'Croppers populated','processed'=>$total,'target'=>$countTotal]; + @mysqli_query($this->dblink, "ANALYZE TABLE `$CROP_TABLE`"); + if ($reporter) { $reporter($total, $countTotal, 100); } + return ['ok'=>true,'msg'=>'Croppers populated','processed'=>$total,'target'=>$countTotal]; + } catch (Throwable $e) { + if ($inTransaction) { + try { + mysqli_rollback($this->dblink); + } catch (Throwable $rollbackException) { + // Ignore rollback errors after fatal DB exceptions. + } + } + return ['ok'=>false,'msg'=>$e->getMessage(),'processed'=>$total,'target'=>$countTotal]; + } } diff --git a/GameEngine/Lang/en.php b/GameEngine/Lang/en.php index 147d9dae..829ddab0 100755 --- a/GameEngine/Lang/en.php +++ b/GameEngine/Lang/en.php @@ -194,7 +194,7 @@ define('WISHES_YOU', 'wishes you'); define('X_MAS', 'Merry Christmas'); define('NEW_YEAR', 'Happy New Year'); define('EASTER', 'Happy Easter'); -define('PEACE', 'Peace'); +if(!defined('PEACE')) define('PEACE', 'Peace'); define('GOLD', 'Gold'); define('GOLD_IMG', '\"'.GOLD.'\"'); @@ -626,8 +626,8 @@ define('MODERATOR', 'Moderator'); define('ACTIVE', 'Active'); define('ONLINE', 'Online'); define('TUTORIAL', 'Tutorial'); -define('FAQ', 'Faq'); -define('SPIELREGELN', 'Game Rules'); +if(!defined('FAQ')) define('FAQ', 'Faq'); +if(!defined('SPIELREGELN')) define('SPIELREGELN', 'Game Rules'); define('PLAYER_STATISTICS', 'Player statistics'); define('TOTAL_PLAYERS', PLAYERS.' in total'); define('ACTIVE_PLAYERS', 'Active players'); @@ -645,8 +645,8 @@ define('BECOME_COMUNITY', 'Become part of our community now!'); define('BECOME_COMUNITY2', 'Become a part of one of
    the biggest gaming
    communities in the
    world.'); define('NEWS', 'News'); define('SCREENSHOTS', 'Screenshots'); -define('FAQ', 'FAQ'); -define('SPIELREGELN', 'Rules'); +if(!defined('FAQ')) define('FAQ', 'FAQ'); +if(!defined('SPIELREGELN')) define('SPIELREGELN', 'Rules'); define('AGB', 'Terms and Conditions'); define('LEARN1', 'Upgrade your fields and mines to increase your resource production. You will need resources to construct buildings and train soldiers.'); define('LEARN2', 'Construct and expand the buildings in your village. Buildings improve your overall infrastructure, increase your resource production and allow you to research, train and upgrade your troops.'); @@ -1331,7 +1331,7 @@ define('PLAYER_ADMIN', 'This player is Admin'); define('PLAYER_MH', 'This player is Multihunter'); define('PLAYER_BANNED', 'This player is BANNED'); define('PLAYER_VACATION', 'This player is on VACATION'); -define('BANNED', 'Banned'); +if(!defined('BANNED')) define('BANNED', 'Banned'); define('GENDER', 'Gender'); define('GENDER0', 'n/a'); define('MALE0', 'm'); diff --git a/GameEngine/Technology.php b/GameEngine/Technology.php index 251e36e9..f28b7d3a 100755 --- a/GameEngine/Technology.php +++ b/GameEngine/Technology.php @@ -684,11 +684,19 @@ class Technology { $resarray['iron'] = ${'r'.$id}['iron']; $resarray['crop'] = ${'r'.$id}['crop']; } - $rwtime = ($resarray['wood']-$village->awood) / $village->getProd("wood") * 3600; - $rcltime = ($resarray['clay']-$village->aclay) / $village->getProd("clay") * 3600; - $ritime = ($resarray['iron']-$village->airon) / $village->getProd("iron") * 3600; - $rctime = ($resarray['crop']-$village->acrop) / $village->getProd("crop") * 3600; - if($village->getProd("crop") >= 0) { + $woodNeed = $resarray['wood'] - $village->awood; + $clayNeed = $resarray['clay'] - $village->aclay; + $ironNeed = $resarray['iron'] - $village->airon; + $cropNeed = $resarray['crop'] - $village->acrop; + $woodProd = $village->getProd("wood"); + $clayProd = $village->getProd("clay"); + $ironProd = $village->getProd("iron"); + $cropProd = $village->getProd("crop"); + $rwtime = ($woodNeed <= 0) ? 0 : ($woodProd > 0 ? $woodNeed / $woodProd * 3600 : 0); + $rcltime = ($clayNeed <= 0) ? 0 : ($clayProd > 0 ? $clayNeed / $clayProd * 3600 : 0); + $ritime = ($ironNeed <= 0) ? 0 : ($ironProd > 0 ? $ironNeed / $ironProd * 3600 : 0); + $rctime = ($cropNeed <= 0) ? 0 : ($cropProd != 0 ? $cropNeed / $cropProd * 3600 : 0); + if($cropProd >= 0) { $reqtime = max($rwtime,$rcltime,$ritime,$rctime) + time(); } else { $reqtime = max($rwtime,$rcltime,$ritime); diff --git a/GameEngine/Units.php b/GameEngine/Units.php index c953d190..2afa9136 100755 --- a/GameEngine/Units.php +++ b/GameEngine/Units.php @@ -24,7 +24,7 @@ class Units { switch($post['c']) { case 1: - if (isset($post['a']) && $post['a'] == 533374) $this->sendTroops($post); + if (isset($post['a']) && $post['a'] == 533374 && empty($post['disabled'])) $this->sendTroops($post); else { $post = $this->loadUnits($post); @@ -51,7 +51,7 @@ class Units { break; case 4: - if (isset($post['a']) && $post['a'] == 533374) $this->sendTroops($post); + if (isset($post['a']) && $post['a'] == 533374 && empty($post['disabled'])) $this->sendTroops($post); else { $post = $this->loadUnits($post); @@ -97,12 +97,24 @@ class Units { public function checkErrors(&$post){ global $database, $village, $session, $generator; + + if(isset($post['x'])) $post['x'] = trim($post['x']); + if(isset($post['y'])) $post['y'] = trim($post['y']); + + if(isset($post['x']) && isset($post['y']) && $post['x'] !== "" && $post['y'] !== ""){ + if(!preg_match('/^-?\d+$/', $post['x']) || !preg_match('/^-?\d+$/', $post['y'])) return "Invalid coordinates"; + + $post['x'] = (int) $post['x']; + $post['y'] = (int) $post['y']; + + if(abs($post['x']) > WORLD_MAX || abs($post['y']) > WORLD_MAX) return "Coordinates do not exist"; + } // Search by town name // Coordinates and look confirm name people if(isset($post['x']) && isset($post['y']) && $post['x'] != "" && $post['y'] != "") { $vid = $database->getVilWref($post['x'], $post['y']); - unset($post['dname'], $post['dname']); + unset($post['dname']); } else if(isset($post['dname']) && !empty($post['dname'])) $vid = $database->getVillageByName(stripslashes($post['dname'])); diff --git a/README.md b/README.md index 205ede99..8df6d085 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,156 @@ -[![Code Triagers Badge](https://www.codetriage.com/shadowss/travianz/badges/users.svg)](https://www.codetriage.com/shadowss/travianz) +# TravianZ + [![Maintenance](https://img.shields.io/maintenance/yes/2025.svg)](https://github.com/Shadowss/TravianZ) [![GitHub Release](https://img.shields.io/github/release/Shadowss/TravianZ/all.svg)](https://github.com/Shadowss/TravianZ) -[![Github All Releases](https://img.shields.io/github/downloads/Shadowss/TravianZ/total.svg)](https://github.com/Shadowss/TravianZ) [![GitHub contributors](https://img.shields.io/github/contributors/Shadowss/TravianZ.svg)](https://github.com/Shadowss/TravianZ) [![license](https://img.shields.io/github/license/Shadowss/TravianZ.svg)](https://github.com/Shadowss/TravianZ) -[![GitHub last commit](https://img.shields.io/github/last-commit/Shadowss/TravianZ.svg)](https://github.com/Shadowss/TravianZ) -[![Proudly Coded in PHPStorm](https://img.shields.io/badge/coded%20in-PHPStorm-BD5CF3.svg)](https://www.jetbrains.com/buy/opensource/?product=phpstorm) -[![Join the chat at https://gitter.im/TravianZ-V8/Lobby](https://badges.gitter.im/TravianZ-V8/Lobby.svg)](https://gitter.im/TravianZ-V8/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Watch how this was made on YouTube](https://img.shields.io/badge/The%20making%20of...-YouTube-FF0000.svg)](https://www.youtube.com/watch?v=1XiHhpGUmQg&list=PLzV5avt1FFHorlIeoL9YX0pdb9bj-FO84) -====== -[![GitHub forks](https://img.shields.io/github/forks/Shadowss/TravianZ.svg?style=social&label=Fork)](https://github.com/Shadowss/TravianZ) -[![GitHub stars](https://img.shields.io/github/stars/Shadowss/TravianZ.svg?style=social&label=Stars)](https://github.com/Shadowss/TravianZ) -[![GitHub watchers](https://img.shields.io/github/watchers/Shadowss/TravianZ.svg?style=social&label=Watch)](https://github.com/Shadowss/TravianZ) -[![GitHub followers](https://img.shields.io/github/followers/Shadowss.svg?style=social&label=Follow)](https://github.com/Shadowss/TravianZ) -[![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/cata7007) -====== -TravianZ Version **v.8.3.5** - BETA 1 -====== -**Note:** this game is still in a pre-release state, although at this point it is very playable, tested and found to be fairly stable +TravianZ is an open-source browser strategy game inspired by classic Travian-like gameplay. -**WARNING:** please note that ***this is in no way an upgrade*** from the old 8.3.2 version, so please ***do not try to just copy your files over***, -since the installer logic has changed and you would just crash your old version +This repository currently targets modern local/server setups with PHP 8.x and MariaDB. -**Docker Quick Start:** +## Project Status -Get up and running quickly with Docker: +- Version line: `v8.3.5` (legacy naming) +- Stability: playable and actively maintained +- Migration note: this is not a drop-in upgrade over older `8.3.2` deployments + +If you are upgrading from an older installation, do a fresh install and migrate data carefully. + +## Quick Start (Docker) ```bash -# Clone the repository git clone https://github.com/Shadowss/TravianZ.git cd TravianZ - -# Copy environment file cp .env.example .env - -# Start containers -docker-compose up -d - -# Open browser to http://localhost:8080/install +docker compose up -d ``` -For detailed Docker setup instructions, see [DOCKER_README.md](DOCKER_README.md) +Then open: -**Quick links:** -* [Download and Updates](https://github.com/Shadowss/TravianZ) »» https://github.com/Shadowss/TravianZ -* [Wiki](https://github.com/Shadowss/TravianZ/wiki) -* [Game Mechanics](http://travian.wikia.com/wiki/Travian_Wiki) -* [The Making Of](https://www.youtube.com/watch?v=1XiHhpGUmQg&list=PLzV5avt1FFHorlIeoL9YX0pdb9bj-FO84) - YouTube Videos about this project -* [Donate to TravianZ](https://www.paypal.me/cata7007) +- `http://localhost:8080/install` -**Minimum requirements:** -* [PHP](http://php.net/) 7.0.0+ -* [MySQL Community Server](https://dev.mysql.com/downloads/mysql/) 5.5+ - * or alternatively, [MariaDB](https://downloads.mariadb.org/) 5.5+ - * Runs fine on Ubuntu 20, Apache2 2.4, MySQL Server 8.0 and PHP 7.4 +Detailed container guide: [DOCKER_README.md](DOCKER_README.md) -**Dedicated or shared hosting?** +## System Requirements -We *strongly* recommend using a ***dedicated hosting*** for this game. Big Travian servers used to host -thousands of players and the original servers still had about 300ms page display time. The legacy code -in this clone (which was created in 2013) is right now doing about **400 MySQL queries** (questions -to the database) per single page refresh - which is usually well beyond what most shared hostings can support -with a big enough player base. +Recommended: -**Support & Bug reports** +- PHP `8.3+` +- MariaDB `latest stable` (or MySQL-compatible server) +- Apache or Nginx with PHP support +- Linux server with enough CPU/RAM for your expected player count -We are usually available to chat at our [Gitter channel](https://gitter.im/TravianZ-V8/Lobby), if you like to ask -any questions or just talk to the developers about how great their day is today :) As for bug reports, please use -the [Issues tab](https://github.com/Shadowss/TravianZ/issues) and create new issue, whether it's an error in game -or you have a feature request to be included in the play. +Notes: -**The team** -* [Shadowss](https://github.com/Shadowss) - project owner and an occasional developer / tester -* [lietuvis10](https://github.com/lietuvis10) - active developer -* [iopietro](https://github.com/iopietro) - alumni developer -* [AL-Kateb](https://github.com/AL-Kateb) - alumni developer -* [martinambrus](https://github.com/martinambrus) - alumni developer -* [Vladyslav](https://github.com/velhbxtyrj) - rigorous game tester -* [phaze1G](https://github.com/phaze1G) - game designer / css support +- The game is query-heavy by design (legacy architecture), so shared hosting can become a bottleneck quickly. +- For medium/large servers, prefer dedicated or well-sized VPS infrastructure. -**Thanks** +## Installation (Web Installer) -Many thanks to all those who recently played around with the project and tested it both, locally and on their -own servers. This includes [ZZJHONS](https://github.com/ZZJHONS), [DracoMilesX](https://github.com/DracoMilesX), -[phaze1G](https://github.com/phaze1G) , [martinambrus](https://github.com/martinambrus) and many others who were too shy to let us know that they use and enjoy -running this game :) +1. Start services (Docker) or prepare your web+DB stack. +2. Open `http://your-host/install`. +3. Fill database settings: + - Host: `db` (Docker) or your DB host + - Port: usually `3306` + - DB/User/Password from your environment +4. Complete installer steps: + - DB structure + - World data + - Croppers build +5. After success, access the game root. -Also, our thanks go to all both, the original and occasional developers, especially [yi12345](https://github.com/yi12345/), -[advocaite](https://github.com/advocaite/), [brainiacX](https://github.com/brainiacX/), [ronix](https://github.com/ronix/), -[MisterX](https://github.com/MisterX/), [Elio](https://github.com/eliopinho/) and many others who were part of this -project's history. +## Environment Configuration -Last but not least, our thanks go to [JetBrains](https://www.jetbrains.com/) for lending us a one-year full-featured -[open-source PHPStorm](https://www.jetbrains.com/buy/opensource/?product=phpstorm) (and other products) license! -Thanks guys, you're awesome :) +Use `.env` (copy from `.env.example`) to manage deployment values. + +Main keys: + +- `MARIADB_ROOT_PASSWORD` +- `MARIADB_DATABASE` +- `MARIADB_USER` +- `MARIADB_PASSWORD` +- `DB_HOST` +- `DB_PORT` + +Legacy compatibility keys (`MYSQL_*`) are still supported and can inherit MariaDB values. + +## Admin Panel + +Admin entrypoint: + +- `http://your-host/Admin/admin.php` + +Recent improvements include: + +- Users list under the `Users` menu +- Better null/undefined handling in admin templates +- Dynamic table prefix support in map tile queries + +## Performance Notes + +For large worlds (for example `400x400`), generation tasks can be expensive. + +Recent optimizations include: + +- world data generation tuning for bulk operations +- croppers generation batching and progress streaming +- safer DB/session handling during installer workflows + +For production-like loads, monitor: + +- DB CPU and slow queries +- PHP-FPM/Apache worker limits +- disk I/O during installer and reset operations + +## Troubleshooting + +Common checks: + +1. If installer cannot connect to DB: + - verify `DB_HOST`, port, user and password + - in Docker, host should be `db`, not `localhost` +2. If permissions fail during install: + - ensure web user can write required runtime files/folders +3. If pages show warnings after PHP upgrade: + - ensure latest code is deployed + - clear opcode/cache and retry + +For container-specific troubleshooting, see [DOCKER_README.md](DOCKER_README.md). + +## Development + +Useful commands: + +```bash +# Start stack +docker compose up -d + +# Logs +docker compose logs -f web + +# Validate PHP files +find . -name '*.php' -not -path './var/*' -print0 | xargs -0 -n1 php -l +``` + +Repository references: + +- Change history: [CHANGELOG.md](CHANGELOG.md) +- Contribution guide: [CONTRIBUTING.md](CONTRIBUTING.md) +- Code of conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) + +## Community and Support + +- Issues: https://github.com/Shadowss/TravianZ/issues +- Wiki: https://github.com/Shadowss/TravianZ/wiki +- Chat: https://gitter.im/TravianZ-V8/Lobby + +## Credits + +Thanks to the original and current maintainers, contributors, testers, and the TravianZ community. + +Special acknowledgement to all legacy authors and maintainers who kept this project alive through multiple iterations. + +## License + +This project is licensed under the terms described in [LICENSE](LICENSE). diff --git a/Security/Security.class.php b/Security/Security.class.php index 9e7e6141..71fca3b1 100644 --- a/Security/Security.class.php +++ b/Security/Security.class.php @@ -71,13 +71,15 @@ class Security if(self::$instance === NULL) { // Check for magic quotes - if(get_magic_quotes_runtime()) + if(function_exists('get_magic_quotes_runtime') && get_magic_quotes_runtime()) { // Dear lord!! This is bad and deprected. Sort it out ;) - set_magic_quotes_runtime(0); + if(function_exists('set_magic_quotes_runtime')) { + set_magic_quotes_runtime(0); + } } - if(get_magic_quotes_gpc()) + if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { // This is also bad and deprected. See http://php.net/magic_quotes for more information. $this->magic_quotes_gpc = TRUE; diff --git a/Templates/Plus/3.tpl b/Templates/Plus/3.tpl index e080d736..e14ae579 100644 --- a/Templates/Plus/3.tpl +++ b/Templates/Plus/3.tpl @@ -52,6 +52,22 @@ $datetimedp = $golds['dp']; // Retrieve the current date/time $date2 = strtotime("NOW"); +function formatRemainingTime($endTimestamp, $nowTimestamp) { + $remaining = (int)$endTimestamp - (int)$nowTimestamp; + if ($remaining <= 0) { + return ''; + } + + $days = intdiv($remaining, 86400); + $remaining %= 86400; + $hours = intdiv($remaining, 3600); + $remaining %= 3600; + $mins = intdiv($remaining, 60); + $secs = $remaining % 60; + + return 'Remaining: '.$days.' days '.$hours.' hours '.$mins.' mins '.$secs.' secs (until '.date('H:i:s', (int)$endTimestamp).')'; +} + if ($datetimep == 0) echo "get PLUS
    "; else { @@ -59,17 +75,7 @@ else print "Your PLUS advantage has ended.
    "; mysqli_query($database->dblink, "UPDATE " . TB_PREFIX . "users set plus = '0' where `id`='" . $session->uid . "'") or die(mysqli_error($database->dblink)); } else { - - $holdtotmin = (($datetimep - $date2) / 60); - $holdtothr = (($datetimep - $date2) / 3600); - $holdtotday = intval(($datetimep - $date2) / 86400); - echo "Remaining: " . $holdtotday . " days"; - - $holdhr = intval($holdtothr - ($holdtotday * 24)); - echo " " . ($holdhr) . " hours "; - - $holdmr = intval($holdtotmin - (($holdhr * 60) + ($holdtotday * 1440))); - echo " " . ($holdmr) . " mins"; + echo "" . formatRemainingTime($datetimep, $date2) . ""; } } ?> @@ -120,23 +126,8 @@ if (PLUS_TIME >= 86400) { Remaining " . $holdtotday1 . " days "; - echo " " . ($holdhr1) . " hours "; - echo " " . ($holdmr1) . " mins "; +if ($tl_b1 >= $date2) { + echo "" . formatRemainingTime($tl_b1, $date2) . " "; } ?>   @@ -189,23 +180,8 @@ if ($session->access != BANNED) { Remaining: " . $holdtotday2 . " days "; - echo " " . ($holdhr2) . " hours "; - echo " " . ($holdmr2) . " mins"; +if ($tl_b2 >= $date2) { + echo "" . formatRemainingTime($tl_b2, $date2) . ""; } ?>   @@ -259,23 +235,8 @@ if ($session->access != BANNED) { Remaining: " . $holdtotday3 . " days "; - echo " " . ($holdhr3) . " hours "; - echo " " . ($holdmr3) . " mins"; +if ($tl_b3 >= $date2) { + echo "" . formatRemainingTime($tl_b3, $date2) . ""; } ?>   @@ -327,23 +288,8 @@ if ($session->access != BANNED) { Remaining: " . $holdtotday4 . " days "; - echo " " . ($holdhr4) . " hours "; - echo " " . ($holdmr4) . " mins"; +if ($tl_b4 >= $date2) { + echo "" . formatRemainingTime($tl_b4, $date2) . ""; } ?>   diff --git a/Templates/Plus/pmenu.tpl b/Templates/Plus/pmenu.tpl index 35ecd9a4..bd2706ec 100644 --- a/Templates/Plus/pmenu.tpl +++ b/Templates/Plus/pmenu.tpl @@ -6,7 +6,7 @@ if(!isset($_GET['id']) && @(basename($_SERVER['REQUEST_URI']) !== 'a2b2.php')) { echo "class=\"selected\""; } - if(isset($_GET['id']) && ($_GET['id'] == 1) || strlen($_GET['id']) === 3) { + if(isset($_GET['id']) && (($_GET['id'] == 1) || strlen((string)$_GET['id']) === 3)) { echo "class=\"selected\""; } diff --git a/build_croppers.php b/build_croppers.php index 753d218e..e25ff6ea 100644 --- a/build_croppers.php +++ b/build_croppers.php @@ -42,6 +42,27 @@ $csrf = $_SESSION['csrf_cb']; function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } +function indexExists($db, $tableName, $indexName) { + $sql = "SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = '".mysqli_real_escape_string($db, $tableName)."' AND index_name = '".mysqli_real_escape_string($db, $indexName)."' LIMIT 1"; + $result = mysqli_query($db, $sql); + if (!$result) { + return false; + } + return mysqli_num_rows($result) > 0; +} + +function createIndexIfMissing($db, $tableName, $indexName, $columnsSql) { + if (!indexExists($db, $tableName, $indexName)) { + mysqli_query($db, "CREATE INDEX `{$indexName}` ON `{$tableName}` ({$columnsSql})"); + } +} + +function dropIndexIfExists($db, $tableName, $indexName) { + if (indexExists($db, $tableName, $indexName)) { + mysqli_query($db, "DROP INDEX `{$indexName}` ON `{$tableName}`"); + } +} + // Ensure table exists (minimal schema, unsigned tinyints) mysqli_query($database->dblink, "CREATE TABLE IF NOT EXISTS `$CROP_TABLE` ( `wref` INT UNSIGNED NOT NULL PRIMARY KEY, @@ -53,10 +74,10 @@ mysqli_query($database->dblink, "CREATE TABLE IF NOT EXISTS `$CROP_TABLE` ( CHECK (`best_oasis_bonus` IN (0,25,50,75,100,125,150)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); -// Helpful indexes (ignore errors if already exist) -@mysqli_query($database->dblink, "CREATE INDEX `idx_ft_bonus_xy` ON `$CROP_TABLE` (`fieldtype`, `best_oasis_bonus`, `x`, `y`)"); -@mysqli_query($database->dblink, "CREATE INDEX `idx_xy` ON `$CROP_TABLE` (`x`, `y`)"); -@mysqli_query($database->dblink, "CREATE INDEX `idx_bonus` ON `$CROP_TABLE` (`best_oasis_bonus`)"); +// Helpful indexes (idempotent, avoids PHP8 mysqli duplicate-index exceptions) +createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_ft_bonus_xy', '`fieldtype`, `best_oasis_bonus`, `x`, `y`'); +createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_xy', '`x`, `y`'); +createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_bonus', '`best_oasis_bonus`'); // ---------- Helpers ---------- function worldSizeLabel(): string { @@ -110,12 +131,12 @@ if ($action === 'truncate') { $notice = "Croppers table truncated."; } if ($action === 'reindex') { - @mysqli_query($database->dblink, "DROP INDEX `idx_ft_bonus_xy` ON `$CROP_TABLE`"); - @mysqli_query($database->dblink, "DROP INDEX `idx_xy` ON `$CROP_TABLE`"); - @mysqli_query($database->dblink, "DROP INDEX `idx_bonus` ON `$CROP_TABLE`"); - @mysqli_query($database->dblink, "CREATE INDEX `idx_ft_bonus_xy` ON `$CROP_TABLE` (`fieldtype`, `best_oasis_bonus`, `x`, `y`)"); - @mysqli_query($database->dblink, "CREATE INDEX `idx_xy` ON `$CROP_TABLE` (`x`, `y`)"); - @mysqli_query($database->dblink, "CREATE INDEX `idx_bonus` ON `$CROP_TABLE` (`best_oasis_bonus`)"); + dropIndexIfExists($database->dblink, $CROP_TABLE, 'idx_ft_bonus_xy'); + dropIndexIfExists($database->dblink, $CROP_TABLE, 'idx_xy'); + dropIndexIfExists($database->dblink, $CROP_TABLE, 'idx_bonus'); + createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_ft_bonus_xy', '`fieldtype`, `best_oasis_bonus`, `x`, `y`'); + createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_xy', '`x`, `y`'); + createIndexIfMissing($database->dblink, $CROP_TABLE, 'idx_bonus', '`best_oasis_bonus`'); $notice = "Indexes rebuilt."; } if ($action === 'estimate') { diff --git a/docker-compose.yml b/docker-compose.yml index 1edb3c95..76b5f520 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: web: build: @@ -10,7 +8,6 @@ services: - "8080:80" volumes: - ./:/var/www/html - - ./var:/var/www/html/var environment: - APACHE_DOCUMENT_ROOT=/var/www/html depends_on: @@ -20,13 +17,13 @@ services: restart: unless-stopped db: - image: mysql:5.7 + image: mariadb:latest container_name: travianz-db environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} - MYSQL_DATABASE: ${MYSQL_DATABASE:-travian} - MYSQL_USER: ${MYSQL_USER:-travianz} - MYSQL_PASSWORD: ${MYSQL_PASSWORD:-travianzpass} + MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-${MYSQL_ROOT_PASSWORD:-rootpassword}} + MARIADB_DATABASE: ${MARIADB_DATABASE:-${MYSQL_DATABASE:-travian}} + MARIADB_USER: ${MARIADB_USER:-${MYSQL_USER:-travianz}} + MARIADB_PASSWORD: ${MARIADB_PASSWORD:-${MYSQL_PASSWORD:-travianzpass}} volumes: - db-data:/var/lib/mysql - ./var/db:/docker-entrypoint-initdb.d:ro @@ -35,7 +32,7 @@ services: networks: - travianz-network restart: unless-stopped - command: --default-authentication-plugin=mysql_native_password --sql_mode="" + command: --sql_mode="" phpmyadmin: image: phpmyadmin/phpmyadmin:latest @@ -44,7 +41,7 @@ services: PMA_HOST: db PMA_PORT: 3306 PMA_USER: root - PMA_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + PMA_PASSWORD: ${MARIADB_ROOT_PASSWORD:-${MYSQL_ROOT_PASSWORD:-rootpassword}} ports: - "8081:80" depends_on: diff --git a/install/ajax_croppers.php b/install/ajax_croppers.php index 5d2e9d28..013b71f5 100644 --- a/install/ajax_croppers.php +++ b/install/ajax_croppers.php @@ -67,8 +67,8 @@ $reporter = function($done, $target, $pct) use (&$lastPing) { if (connection_aborted()) { exit; } // client left }; -// Run it (fresh world => truncateFirst=true; big batch on dedicated server) -$out = $database->populateCroppers($total, true, 20000, $reporter); +// Run it (fresh world => truncateFirst=true). Slightly smaller batch keeps progress updates more frequent. +$out = $database->populateCroppers($total, true, 2000, $reporter); if (!empty($out['ok'])) { sse_send([ @@ -79,6 +79,7 @@ if (!empty($out['ok'])) { ]); } else { sse_send([ + 'error'=>true, 'pct'=>0, 'done'=>0, 'total'=>(int)$total, diff --git a/install/index.php b/install/index.php index 7586f6a0..6165efca 100644 --- a/install/index.php +++ b/install/index.php @@ -7,7 +7,7 @@ include("templates/script.tpl"); if(!isset($_GET['s'])) { $_GET['s']=0; } -$tz=(isset($_GET['t']))? $_GET['t']:1; +$tz=(isset($_GET['t']))? (int)$_GET['t'] : 13; switch($tz) { case 1: $t_zone="Africa/Dakar";break; case 2: $t_zone="America/New_York";break; @@ -18,8 +18,11 @@ $tz=(isset($_GET['t']))? $_GET['t']:1; case 7: $t_zone="Australia/Melbourne";break; case 8: $t_zone="Europe/Bucharest";break; case 9: $t_zone="Europe/London";break; - case 10: $t_zone="Indian/Maldives";break; - case 11: $t_zone="Pacific/Fiji";break; + case 10: $t_zone="Europe/Bratislava";break; + case 11: $t_zone="Indian/Maldives";break; + case 12: $t_zone="Pacific/Fiji";break; + case 13: $t_zone="America/Sao_Paulo";break; + default: $t_zone="America/Sao_Paulo";break; } date_default_timezone_set($t_zone); ?> diff --git a/install/templates/config.tpl b/install/templates/config.tpl index db221626..e674eecc 100644 --- a/install/templates/config.tpl +++ b/install/templates/config.tpl @@ -19,6 +19,64 @@ if(isset($_GET['c']) && $_GET['c'] == 1) { echo "
    Error creating constant.php check cmod.

    "; } + +@session_start(); + +$envPath = dirname(__DIR__, 2) . '/.env'; +$envDefaults = []; +if(file_exists($envPath)) { + $lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if(is_array($lines)) { + foreach($lines as $line) { + $line = trim($line); + if($line === '' || $line[0] === '#') { + continue; + } + + $eqPos = strpos($line, '='); + if($eqPos === false) { + continue; + } + + $key = trim(substr($line, 0, $eqPos)); + $value = trim(substr($line, $eqPos + 1)); + if($key === '') { + continue; + } + + if((strlen($value) >= 2) && (($value[0] === '"' && substr($value, -1) === '"') || ($value[0] === "'" && substr($value, -1) === "'"))) { + $value = substr($value, 1, -1); + } + + $envDefaults[$key] = $value; + } + + // Resolve ${VAR} references using parsed values first, then process env. + foreach($envDefaults as $key => $value) { + $envDefaults[$key] = preg_replace_callback('/\$\{([A-Z0-9_]+)\}/i', function($m) use ($envDefaults) { + $ref = $m[1]; + if(isset($envDefaults[$ref])) return $envDefaults[$ref]; + $fromEnv = getenv($ref); + return ($fromEnv !== false) ? $fromEnv : ''; + }, $value); + } + } +} + +$dbHost = $envDefaults['DB_HOST'] ?? 'localhost'; +$dbPort = $envDefaults['DB_PORT'] ?? '3306'; +$dbUser = $envDefaults['MARIADB_USER'] ?? ($envDefaults['MYSQL_USER'] ?? ''); +$dbPass = $envDefaults['MARIADB_PASSWORD'] ?? ($envDefaults['MYSQL_PASSWORD'] ?? ''); +$dbName = $envDefaults['MARIADB_DATABASE'] ?? ($envDefaults['MYSQL_DATABASE'] ?? ''); + +if(empty($_SESSION['install_random_prefix'])) { + try { + $_SESSION['install_random_prefix'] = 's' . substr(bin2hex(random_bytes(2)), 0, 4) . '_'; + } catch (Throwable $e) { + $_SESSION['install_random_prefix'] = 's' . str_pad((string) mt_rand(0, 9999), 4, '0', STR_PAD_LEFT) . '_'; + } +} +$dbPrefix = $_SESSION['install_random_prefix']; ?>
    @@ -41,6 +99,7 @@ echo "
    Error creating constant.php + Port: - + Username: - + Password: - + DB name: - + Prefix: - + Type: diff --git a/mailme.php b/mailme.php index c5bb0503..20604e2a 100644 --- a/mailme.php +++ b/mailme.php @@ -24,22 +24,22 @@ if($_POST && count($_POST)) $strMailtext = ""; - while(list($strName,$value) = each($_POST)) - { - if(is_array($value)) - { - foreach($value as $value_array) - { - $strMailtext .= $strName.$strDelimiter.$value_array."\n"; - } - } - else - { - $strMailtext .= $strName.$strDelimiter.$value."\n"; - } - } + foreach($_POST as $strName => $value) + { + if(is_array($value)) + { + foreach($value as $value_array) + { + $strMailtext .= $strName.$strDelimiter.$value_array."\n"; + } + } + else + { + $strMailtext .= $strName.$strDelimiter.$value."\n"; + } + } - if(get_magic_quotes_gpc()) + if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { $strMailtext = stripslashes($strMailtext); } diff --git a/massmessage.php b/massmessage.php index 67ad6d1c..fbd663af 100644 --- a/massmessage.php +++ b/massmessage.php @@ -17,6 +17,11 @@ AccessLogger::logRequest(); $max_per_pass = 1000; +// Default flow flags used later in template conditions. +$NextStep = false; +$NextStep2 = false; +$done = false; + if (mysqli_num_rows(mysqli_query($database->dblink,"SELECT id FROM ".TB_PREFIX."users WHERE access = 9 AND id = ".(int) $session->uid)) != '1') die("Hacking attemp!"); if (@$_POST['submit'] == "Send") diff --git a/sysmsg.php b/sysmsg.php index 6d4d79e4..b77fbe5a 100644 --- a/sysmsg.php +++ b/sysmsg.php @@ -15,6 +15,13 @@ include_once("GameEngine/Account.php"); AccessLogger::logRequest(); $max_per_pass = 1000; + +// Default flow flags used later in template conditions. +$NextStep = false; +$NextStep2 = false; +$done = false; +$Interupt = false; + if (mysqli_num_rows(mysqli_query($database->dblink,"SELECT id FROM ".TB_PREFIX."users WHERE access = 9 AND id = ".$session->uid)) != '1') die("Hacking attempt!"); if(isset($_GET['del'])){