diff --git a/GameEngine/Account.php b/GameEngine/Account.php index 65a0570c..61221e6a 100755 --- a/GameEngine/Account.php +++ b/GameEngine/Account.php @@ -360,19 +360,42 @@ class Account { // Vacation mode by Shadow $database->removevacationmode($userData['id']); + /** + * Marcam sesiunea ca fiind de SITTER si retinem CARE sitter e. + * + * Pana acum, dupa autentificare sesiunea unui sitter era identica cu a + * proprietarului - nu exista nimic in $_SESSION care sa spuna ca cel din + * spatele tastaturii e altcineva. Flagul "sit" din tabela online nu ajuta: + * e per cont (nu per sesiune), se scrie cu INSERT IGNORE (deci nu se + * actualizeaza daca randul exista deja) si nu spune care dintre cei doi + * sitteri s-a conectat. + * + * $_SESSION['sitter_uid'] e sursa de adevar pentru permisiuni. + */ + $sitterUid = 0; + if ($database->login($username, $password)) { $database->UpdateOnline("login", $username, time(), $userData['id']); - } elseif ($database->sitterLogin($username, $password)) { - $database->UpdateOnline("sitter", $username, time(), $userData['id']); + } else { + $sitterUid = (int) $database->sitterLogin($username, $password); + + if ($sitterUid > 0) { + $database->UpdateOnline("sitter", $username, time(), $userData['id']); + } } setcookie("COOKUSR", $username, time() + COOKIE_EXPIRE, COOKIE_PATH); $session->login($username); + + // dupa Login(), ca sa nu fie suprascris de initializarea sesiunii + $_SESSION['sitter_uid'] = $sitterUid; } private function Logout() { global $session, $database; unset($_SESSION['wid']); + // altfel un login normal facut imediat dupa ar mosteni flagul de sitter + unset($_SESSION['sitter_uid']); // actualizează statusul "activ" al utilizatorului $database->activeModify($database->escape($session->username), 1); // actualizează ultima activitate online diff --git a/GameEngine/AllianceBonus.php b/GameEngine/AllianceBonus.php index f3aecac4..3899e579 100644 --- a/GameEngine/AllianceBonus.php +++ b/GameEngine/AllianceBonus.php @@ -556,8 +556,17 @@ class AllianceBonus if ($triple) { $cost = defined('ALLIANCE_BONUS_TRIPLE_GOLD') ? (int) ALLIANCE_BONUS_TRIPLE_GOLD : 3; - $database->modifyGold($uid, $cost, 0); - $session->gold -= $cost; + + // PERMISIUNI SITTER: donatia tripla costa aur. Daca sitterul nu are + // dreptul, donatia simpla (deja inregistrata mai sus) ramane valida, + // doar bonusul platit nu se aplica. + if (!isset($session) || !method_exists($session, 'sitterCan') + || $session->sitterCan(SITTER_PERM_GOLD)) { + + if ($database->spendGold($uid, $cost, 'Alliance bonus triple donation')) { + $session->gold -= $cost; + } + } } mysqli_query($this->db, "COMMIT"); diff --git a/GameEngine/Building.php b/GameEngine/Building.php index 390b6856..84ef9ceb 100755 --- a/GameEngine/Building.php +++ b/GameEngine/Building.php @@ -1604,9 +1604,26 @@ class Building { } $spent = ($countMasterGold && $countPlus2Gold) ? 3 : 2; - $newgold = $this->sess->gold - $spent; - $this->db->updateUserField($this->sess->uid, 'gold', $newgold, 1); + /** + * PERMISIUNI SITTER: "cheltuie aur". + * Verificam inainte de scadere; daca nu are voie, nu se ia nimic. + */ + if (isset($this->sess) && method_exists($this->sess, 'sitterCan') + && !$this->sess->sitterCan(SITTER_PERM_GOLD)) { + return; + } + + /** + * spendGold() in loc de scriere absoluta: scaderea e relativa si atomica, + * deci dispare riscul de dubla cheltuire descris in comentariul de mai jos + * (soldul din sesiune putea fi vechi de pana la 30 de secunde). + */ + if (!$this->db->spendGold($this->sess->uid, $spent, 'Finish all constructions')) { + return; + } + + $newgold = $this->sess->gold - $spent; $this->db->addGoldFinLog( $this->vil->wid, diff --git a/GameEngine/Database/DatabaseUserQueries.php b/GameEngine/Database/DatabaseUserQueries.php index d8f618c9..459b64d4 100644 --- a/GameEngine/Database/DatabaseUserQueries.php +++ b/GameEngine/Database/DatabaseUserQueries.php @@ -382,9 +382,32 @@ trait DatabaseUserQueries { return $cachedResult; } + /** @var array cache per request: username => uid-ul sitterului care s-a potrivit */ + private static $sitterLoginCache = array(); + + /** + * Autentificare ca sitter pe contul $username. + * + * INTOARCE ACUM UID-UL SITTERULUI (0 = esec), nu doar true/false. + * Fara asta nu se putea sti CARE dintre cei doi sitteri s-a conectat, + * deci nu aveai cum sa aplici permisiuni diferite pentru sit1 si sit2. + * Un uid e mereu > 0, iar 0 e falsy, deci apelurile vechi de forma + * "if ($database->sitterLogin(...))" continua sa functioneze identic. + * + * Account.php o cheama de doua ori pe acelasi login (o data la validare, + * o data ca sa scrie in tabela online). Cache-ul de mai jos face ca a + * doua verificare de parola sa nu se mai execute - password_verify e + * intentionat lent, deci conteaza. + */ function sitterLogin($username, $password) { list($username, $password) = $this->escape_input($username, $password); + $cacheKey = $username . '|' . md5($password); + + if (isset(self::$sitterLoginCache[$cacheKey])) { + return self::$sitterLoginCache[$cacheKey]; + } + $q = "SELECT sit1,sit2 FROM " . TB_PREFIX . "users where username = '$username' and access != " . BANNED ." LIMIT 1"; $result = mysqli_query($this->dblink,$q); $dbarray = mysqli_fetch_array($result); @@ -395,7 +418,7 @@ trait DatabaseUserQueries { * null". Iesim din start - oricum nu are cine sa fie sitter. */ if (!is_array($dbarray)) { - return false; + return self::$sitterLoginCache[$cacheKey] = 0; } $dbarray2 = null; @@ -412,16 +435,19 @@ trait DatabaseUserQueries { $dbarray3 = mysqli_fetch_array($result3); } if($dbarray['sit1'] != 0 || $dbarray['sit2'] != 0) { + // Verificam separat, ca sa stim CARE sitter s-a potrivit. // sit1/sit2 pot fi setati independent, deci unul dintre randuri - // poate lipsi; fara ?? '' iesea acelasi warning pe null. - if(password_verify($password, (string) ($dbarray2['password'] ?? '')) - || password_verify($password, (string) ($dbarray3['password'] ?? ''))) { - return true; + // poate lipsi; fara ?? '' iesea warning pe null. + if (password_verify($password, (string) ($dbarray2['password'] ?? ''))) { + return self::$sitterLoginCache[$cacheKey] = (int) $dbarray['sit1']; + } else if (password_verify($password, (string) ($dbarray3['password'] ?? ''))) { + return self::$sitterLoginCache[$cacheKey] = (int) $dbarray['sit2']; } else { - return false; + return self::$sitterLoginCache[$cacheKey] = 0; } } else { - return false; + // contul nu are niciun sitter definit + return self::$sitterLoginCache[$cacheKey] = 0; } } @@ -446,6 +472,50 @@ trait DatabaseUserQueries { return isset($dbarray['timestamp']) ? (int)$dbarray['timestamp'] : 0; } + /** + * PUNCTUL UNIC DE CHELTUIRE A AURULUI. + * + * Scade $amount aur de la $uid, ATOMIC: conditia "gold >= amount" e in + * acelasi UPDATE, deci doua cereri simultane nu pot scoate soldul pe minus. + * Intoarce true doar daca s-a scazut efectiv. + * + * De ce exista: pana acum cheltuirea era imprastiata in ~11 locuri, iar + * o parte scriau ABSOLUT ("gold = $session->gold - 2"). Scrierea absoluta + * porneste de la soldul din sesiune, care poate fi vechi de pana la 30 de + * secunde (cache-ul din Session::PopulateVar) - de aici riscul de dubla + * cheltuire pe care il semnalau si comentariile din Building.php si + * build.php. Scaderea relativa nu are problema asta. + * + * NU verifica permisiunile de sitter: gardul se pune in stratul de mai sus, + * unde exista context pentru mesajul de eroare si pentru redirect. + */ + function spendGold($uid, $amount, $reason = '') { + + $uid = (int) $uid; + $amount = (int) $amount; + + if ($uid <= 0 || $amount <= 0) { + return false; + } + + $q = "UPDATE " . TB_PREFIX . "users SET gold = gold - " . $amount + . " WHERE id = " . $uid . " AND gold >= " . $amount; + + mysqli_query($this->dblink, $q); + + if (mysqli_affected_rows($this->dblink) != 1) { + return false; + } + + // soldul s-a schimbat in baza de date; cache-ul de sesiune trebuie + // invalidat, altfel urmatoarea cerere ar reciti vechea valoare + if (isset($_SESSION['username'])) { + unset($_SESSION['cache_user_' . $_SESSION['username']]); + } + + return true; + } + function modifyGold($userid, $amt, $mode) { list($userid, $amt, $mode) = $this->escape_input((int) $userid, (int) $amt, $mode); diff --git a/GameEngine/Lang/en.php b/GameEngine/Lang/en.php index 399ee391..d7146f0a 100755 --- a/GameEngine/Lang/en.php +++ b/GameEngine/Lang/en.php @@ -4383,3 +4383,13 @@ tz_def('HERO_HEADER_RETURN_HOME', 'Hero returning home'); tz_def('HERO_HEADER_PER_HOUR', 'per hour'); tz_def('TZ_HEALTH', 'Health'); +////////////////////////////////////////////////////////////////////////////////////////////////////// +// PERMISIUNI SITTER +////////////////////////////////////////////////////////////////////////////////////////////////////// +tz_def('SITTER_P_ATTACK', 'send attacks'); +tz_def('SITTER_P_RAID', 'send raids'); +tz_def('SITTER_P_REINF', 'send reinforcements'); +tz_def('SITTER_P_RES', 'send resources to other players'); +tz_def('SITTER_P_GOLD', 'spend Gold'); +tz_def('SITTER_P_DENIED', 'Your sitter permissions do not allow this action.'); + diff --git a/GameEngine/Lang/fr.php b/GameEngine/Lang/fr.php index 319e7cad..6354207f 100644 --- a/GameEngine/Lang/fr.php +++ b/GameEngine/Lang/fr.php @@ -4325,3 +4325,13 @@ tz_def('HERO_HEADER_RETURN_ADV', 'Le heros revient de l\'aventure'); tz_def('HERO_HEADER_RETURN_HOME', 'Le heros rentre chez lui'); tz_def('HERO_HEADER_PER_HOUR', 'par heure'); tz_def('TZ_HEALTH', 'Sante'); + +////////////////////////////////////////////////////////////////////////////////////////////////////// +// PERMISSIONS DU SITTER +////////////////////////////////////////////////////////////////////////////////////////////////////// +tz_def('SITTER_P_ATTACK', 'envoyer des attaques'); +tz_def('SITTER_P_RAID', 'envoyer des raids'); +tz_def('SITTER_P_REINF', 'envoyer des renforts'); +tz_def('SITTER_P_RES', 'envoyer des ressources à d’autres joueurs'); +tz_def('SITTER_P_GOLD', 'dépenser de l’or'); +tz_def('SITTER_P_DENIED', 'Vos permissions de sitter ne vous permettent pas d’effectuer cette action.'); \ No newline at end of file diff --git a/GameEngine/Lang/ro.php b/GameEngine/Lang/ro.php index ea51939e..fc4443ce 100644 --- a/GameEngine/Lang/ro.php +++ b/GameEngine/Lang/ro.php @@ -4119,3 +4119,14 @@ tz_def('HERO_HEADER_ATTACK', 'Eroul este plecat cu armata'); tz_def('HERO_HEADER_REINFORCE', 'Eroul este intr-o intarire'); tz_def('HERO_HEADER_IN', 'in'); tz_def('TZ_HEALTH', 'Sanatate'); + + +////////////////////////////////////////////////////////////////////////////////////////////////////// +// PERMISIUNI SITTER +////////////////////////////////////////////////////////////////////////////////////////////////////// +tz_def('SITTER_P_ATTACK', 'trimite atacuri'); +tz_def('SITTER_P_RAID', 'trimite raiduri'); +tz_def('SITTER_P_REINF', 'trimite intariri'); +tz_def('SITTER_P_RES', 'trimite resurse altor jucatori'); +tz_def('SITTER_P_GOLD', 'cheltuie aur'); +tz_def('SITTER_P_DENIED', 'Permisiunile tale de sitter nu iti permit aceasta actiune.'); \ No newline at end of file diff --git a/GameEngine/Market.php b/GameEngine/Market.php index 29cf7c91..042ab42a 100755 --- a/GameEngine/Market.php +++ b/GameEngine/Market.php @@ -290,9 +290,50 @@ class Market // Keeping logic safe while preserving compatibility. $id = isset($post['getwref']) ? (int)$post['getwref'] : 0; - // Check vacation mode + /** + * PERMISIUNI SITTER: "trimite resurse altor jucatori". + * + * Restrictia se aplica DOAR catre alte conturi. Mutarea resurselor + * intre satele proprii ramane permisa - altfel un sitter n-ar mai putea + * administra contul deloc, ceea ce nu e scopul. + * + * Verificam proprietarul satului tinta, nu numele lui: un jucator poate + * avea oricate sate, iar comparatia pe uid e singura de incredere. + */ + if ($id > 0 && isset($session) && is_object($session) && method_exists($session, 'sitterCan')) { + + $targetOwner = (int) $database->getVillageField($id, 'owner'); + + if ($targetOwner > 0 + && $targetOwner !== (int) $session->uid + && !$session->sitterCan(SITTER_PERM_RES)) { + + $form->addError('error', defined('SITTER_P_DENIED') + ? SITTER_P_DENIED + : 'Your sitter permissions do not allow this action.'); + + /** + * "return", nu doar addError: mai jos urmeaza un lant + * if/elseif care ajunge la ramura de trimitere indiferent de + * erorile adaugate inainte. Fara iesire explicita, resursele + * ar fi plecat oricum, iar mesajul de eroare ar fi fost doar + * decorativ. + */ + return; + } + } + + /** + * BUG PREEXISTENT: modul vacanta era doar semnalat, nu si blocat. + * + * Eroarea se adauga aici, dar mai jos urmeaza un lant if/elseif care + * ajunge la ramura de trimitere indiferent de erorile de dinainte. + * Rezultatul: mesajul "utilizatorul e in vacanta" aparea pe ecran, dar + * resursele plecau oricum catre contul protejat. Iesim explicit. + */ if ($database->getvacmodexy($id)) { $form->addError('error', USER_ON_VACATION); + return; } if (!$database->checkVilExist($post['getwref'])) { @@ -878,6 +919,21 @@ class Market exit; } + /** + * PERMISIUNI SITTER: schimbul NPC costa 3 aur. + * + * Gardul TREBUIE sa fie aici, nu langa plata de la finalul functiei: + * redistribuirea resurselor (setVillageField) se executa INAINTE de + * scaderea aurului, deci un garda pus doar pe plata ar fi oferit + * sitterului NPC gratuit. + */ + if (isset($session) && method_exists($session, 'sitterCan') + && !$session->sitterCan(SITTER_PERM_GOLD)) { + + header('Location: build.php?id=' . $post['id'] . '&t=3'); + exit; + } + // Sanitize the requested distribution: never negative, never above the // warehouse / granary capacity. Guards against a forged or NaN-corrupted // POST (issue #211: NPC distribution). @@ -917,7 +973,9 @@ class Market ] ); $this->forget(); - $database->modifyGold($session->uid, 3, 0); + + // permisiunea a fost verificata la intrarea in functie + $database->spendGold($session->uid, 3, 'NPC merchant'); header('Location: build.php?id=' . $post['id'] . '&t=3&c'); exit; diff --git a/GameEngine/Profile.php b/GameEngine/Profile.php index 97f306bc..355ecc6b 100755 --- a/GameEngine/Profile.php +++ b/GameEngine/Profile.php @@ -448,6 +448,31 @@ class Profile { } } + /** + * Transforma un set de casute bifate intr-o masca de biti. + * + * Acceptam doar valorile din SITTER_PERM_ALL: orice altceva venit prin + * POST e ignorat, ca un jucator sa nu-si poata inventa biti noi. + * Un array gol inseamna "niciun drept" - stare perfect valida, deci NU + * cadem pe SITTER_PERM_ALL cand lipseste continutul. + */ + $sitterMaskFromPost = function ($raw) { + + $mask = 0; + + if (is_array($raw)) { + foreach ($raw as $bit) { + $bit = (int) $bit; + + if ($bit > 0 && ($bit & SITTER_PERM_ALL) === $bit) { + $mask |= $bit; + } + } + } + + return $mask; + }; + // Sitter assignment if (!empty($post['v1'])) { @@ -458,15 +483,55 @@ class Profile { } else if ($sitid != $session->uid) { + // permisiunile cu care intra noul sitter + $newPerm = $sitterMaskFromPost($post['perm_new'] ?? null); + if ($session->userinfo['sit1'] == 0) { $database->updateUserField($session->uid, "sit1", $sitid, 1); + $database->updateUserField($session->uid, "sit1_perm", $newPerm, 1); } else if ($session->userinfo['sit2'] == 0) { $database->updateUserField($session->uid, "sit2", $sitid, 1); + $database->updateUserField($session->uid, "sit2_perm", $newPerm, 1); } } } + /** + * Actualizarea permisiunilor pentru sitterii deja existenti. + * + * Casutele sunt in acelasi formular cu restul setarilor de cont, deci + * ajung aici la orice salvare. Scriem doar pentru sloturile ocupate - + * altfel am seta permisiuni pentru un sitter inexistent. + * + * Atentie: casutele nebifate NU se trimit prin POST. De aceea ne uitam + * dupa cheia campului, nu dupa continutul ei: daca perm1 lipseste cu + * totul inseamna ca formularul nu continea sectiunea (nu ca sitterul + * si-a pierdut toate drepturile). + */ + foreach (array(1 => 'sit1', 2 => 'sit2') as $slot => $key) { + + if ((int) $session->userinfo[$key] === 0) { + continue; + } + + $field = 'perm' . $slot; + + // slotul e afisat in formular doar cand e ocupat; daca butonul de + // salvare a fost apasat, campul exista chiar si gol (vezi hidden-ul + // din account.tpl) + if (!isset($post[$field . '_sent'])) { + continue; + } + + $database->updateUserField( + $session->uid, + $key . '_perm', + $sitterMaskFromPost($post[$field] ?? null), + 1 + ); + } + // Persist errors if any if ($form->returnErrors() > 0) { $_SESSION['errorarray'] = $form->getErrors(); diff --git a/GameEngine/Session.php b/GameEngine/Session.php index 6e08e10f..954543fd 100755 --- a/GameEngine/Session.php +++ b/GameEngine/Session.php @@ -1,5 +1,17 @@ checker = $_SESSION['checker']; $this->mchecker = $_SESSION['mchecker']; - $this->sit = $database->GetOnline($this->uid); + /** + * $this->sit (0 = proprietar, 1 = sitter) se calculeaza acum din + * sesiune, nu din tabela "online". + * + * GetOnline() citea flagul scris de UpdateOnline() cu INSERT IGNORE - + * adica flagul NU se actualiza daca randul exista deja. In practica: + * - proprietarul care se loga dupa un sitter mostenea sit = 1 si era + * blocat sa cumpere Plus pe propriul cont; + * - sitterul care se loga dupa proprietar primea sit = 0 si trecea de + * toate gardurile. + * Flagul din sesiune e per-sesiune si mereu corect, deci il folosim ca + * sursa. Toti consumatorii existenti de $session->sit (a2b.php, + * alli_menu.tpl, Profile/overview.tpl, Templates/Plus/*) se repara + * automat, fara sa fie atinsi. + * + * Nota: sesiunile deschise INAINTE de acest update nu au sitter_uid, + * deci un sitter deja conectat va aparea ca proprietar pana la + * urmatoarea autentificare. + */ + $this->sit = (isset($_SESSION['sitter_uid']) && (int) $_SESSION['sitter_uid'] > 0) ? 1 : 0; $this->sit1 = $this->userarray['sit1']; $this->sit2 = $this->userarray['sit2']; + /** + * Permisiunile sesiunii curente. + * + * Recitim sit1/sit2 din contul proprietarului la FIECARE cerere, nu + * doar la login: daca proprietarul retrage un sitter sau ii taie un + * drept in timp ce acesta e conectat, modificarea se aplica imediat. + * + * Daca sitterul conectat nu mai apare in sit1/sit2 (a fost sters + * intre timp), masca devine 0 - adica nu mai are voie nimic. + */ + $this->sitterUid = isset($_SESSION['sitter_uid']) ? (int) $_SESSION['sitter_uid'] : 0; + $this->sitterPerm = SITTER_PERM_ALL; + + if ($this->sitterUid > 0) { + + if ((int) $this->userarray['sit1'] === $this->sitterUid) { + $this->sitterPerm = isset($this->userarray['sit1_perm']) + ? (int) $this->userarray['sit1_perm'] + : SITTER_PERM_ALL; + + } else if ((int) $this->userarray['sit2'] === $this->sitterUid) { + $this->sitterPerm = isset($this->userarray['sit2_perm']) + ? (int) $this->userarray['sit2_perm'] + : SITTER_PERM_ALL; + + } else { + $this->sitterPerm = 0; + } + } + $this->cp = floor($this->userarray['cp']); $this->gold = $this->userarray['gold']; $this->oldrank = $this->userarray['oldrank']; @@ -606,6 +687,45 @@ function __construct() { } } } + + /** + * Sesiunea curenta apartine unui sitter, nu proprietarului contului? + */ + public function isSitterSession() + { + return $this->sitterUid > 0; + } + + /** + * Are sesiunea curenta dreptul $perm (una din constantele SITTER_PERM_*)? + * + * Proprietarul primeste mereu true. Se verifica la EXECUTIE, pe server - + * nu e suficient sa ascunzi butoanele, fiindca un sitter poate trimite + * POST-ul direct. + */ + /** + * Scurtatura pentru cel mai des folosit drept. + * Intoarce mesajul de eroare gata de afisat, sau "" daca are voie. + */ + public function sitterGoldError() + { + if ($this->sitterCan(SITTER_PERM_GOLD)) { + return ""; + } + + return defined('SITTER_P_DENIED') + ? SITTER_P_DENIED + : 'Your sitter permissions do not allow this action.'; + } + + public function sitterCan($perm) + { + if (!$this->isSitterSession()) { + return true; + } + + return ((int) $this->sitterPerm & (int) $perm) === (int) $perm; + } } $session = new Session; diff --git a/GameEngine/Units.php b/GameEngine/Units.php index de039af5..b2444b54 100755 --- a/GameEngine/Units.php +++ b/GameEngine/Units.php @@ -76,6 +76,47 @@ class Units { * @return string Returns the errors, or empty if no errors was found */ + /** + * PERMISIUNI SITTER - misiune -> drept necesar. + * + * Codurile de misiune vin din formularul de trimitere (Templates/a2b/): + * 1 = spionaj -> permis mereu (nu e in lista din Travian) + * 2 = intarire -> SITTER_PERM_REINF + * 3 = atac normal -> SITTER_PERM_ATTACK + * 4 = raid -> SITTER_PERM_RAID + * 5 = colonizare -> permis mereu + * + * Intoarce mesajul de eroare, sau "" daca actiunea e permisa. + * Proprietarul contului primeste mereu "". + */ + private function sitterMissionError($missionType) { + global $session; + + if (!isset($session) || !is_object($session) || !method_exists($session, 'sitterCan')) { + return ""; + } + + $map = array( + 2 => SITTER_PERM_REINF, + 3 => SITTER_PERM_ATTACK, + 4 => SITTER_PERM_RAID, + ); + + $missionType = (int) $missionType; + + if (!isset($map[$missionType])) { + return ""; + } + + if ($session->sitterCan($map[$missionType])) { + return ""; + } + + return defined('SITTER_P_DENIED') + ? SITTER_P_DENIED + : 'Your sitter permissions do not allow this action.'; + } + public function checkErrors(&$post){ global $database, $generator, $session; @@ -87,6 +128,9 @@ class Units { if(!empty($disabled) && $post['c'] == 3) return "You can't attack this village/oasis with normal attack"; if($post['c'] < 2 || $post['c'] > 4) return "Invalid attack type."; + // sitterul fara dreptul respectiv nu ajunge nici macar la confirmare + if(($error = $this->sitterMissionError($post['c'])) !== "") return $error; + //check if at least one troops has been selected $selectedTroops = 0; for($i = 1; $i <= 11; $i++) $selectedTroops += empty($post['t'.$i]) ? 0 : $post['t'.$i]; @@ -344,6 +388,22 @@ class Units { $data = $database->getA2b($post['timestamp_checksum']); $Gtribe = ($session->tribe == 1) ? "" : $session->tribe - 1; + /** + * A DOUA verificare, cea care conteaza cu adevarat. + * + * checkErrors() ruleaza pe formular, dar un sitter poate trimite POST-ul + * de confirmare direct, sarind peste el. Aici e ultimul punct inainte ca + * trupele sa fie scazute din sat, deci verificam din nou - pe tipul REAL + * al misiunii, citit din baza de date ($data['type']), nu din $_POST. + */ + if (($sitterError = $this->sitterMissionError($data['type'] ?? 0)) !== "") { + $form->addError("error", $sitterError); + $_SESSION['errorarray'] = $form->getErrors(); + $_SESSION['valuearray'] = $_POST; + header("Location: a2b.php"); + exit; + } + for ($i = 1; $i < 10; $i++) { if (isset($data['u'.$i])) { if ($data['u'.$i] > $village->unitarray['u'.$Gtribe.$i]) { @@ -965,6 +1025,17 @@ class Units { public function startRaidList($post){ global $database, $generator, $session; + /** + * Lista de ferme trimite raiduri, deci intra tot sub SITTER_PERM_RAID. + * Fara garda aici, un sitter caruia i s-ai taiat raidurile le-ar fi + * putut porni oricum din Punctul de Adunare. + */ + if (($sitterError = $this->sitterMissionError(4)) !== "") { + $_SESSION['errorarray'] = array($sitterError); + header("Location: build.php?id=39&t=99"); + exit(); + } + $slots = $post['slot'] ?? []; if(empty($slots)){ header("Location: build.php?id=39&t=99"); diff --git a/Templates/Plus/10.tpl b/Templates/Plus/10.tpl index bb003abe..1ef4551d 100644 --- a/Templates/Plus/10.tpl +++ b/Templates/Plus/10.tpl @@ -18,7 +18,11 @@ ## --------------------------------------------------------------------------- ## ################################################################################# -if($session->sit == 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if($session->sitterCan(SITTER_PERM_GOLD)) { $uid = (int)$session->uid; $wid = (int)$village->wid; $now = time(); diff --git a/Templates/Plus/11.tpl b/Templates/Plus/11.tpl index 5cb85f67..74a3e2f5 100644 --- a/Templates/Plus/11.tpl +++ b/Templates/Plus/11.tpl @@ -18,7 +18,11 @@ ## --------------------------------------------------------------------------- ## ################################################################################# -if($session->sit == 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if($session->sitterCan(SITTER_PERM_GOLD)) { $uid = (int)$session->uid; $wid = (int)$village->wid; $now = time(); diff --git a/Templates/Plus/12.tpl b/Templates/Plus/12.tpl index 28108e4f..4085ee60 100644 --- a/Templates/Plus/12.tpl +++ b/Templates/Plus/12.tpl @@ -18,7 +18,11 @@ ## --------------------------------------------------------------------------- ## ################################################################################# -if($session->sit == 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if($session->sitterCan(SITTER_PERM_GOLD)) { $uid = (int)$session->uid; $wid = (int)$village->wid; $now = time(); diff --git a/Templates/Plus/15.tpl b/Templates/Plus/15.tpl index 8dee18eb..63de1d2c 100644 --- a/Templates/Plus/15.tpl +++ b/Templates/Plus/15.tpl @@ -24,7 +24,11 @@ $wid = (int)$village->wid; $now = time(); $cost = 100; -if($session->sit != 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if(!$session->sitterCan(SITTER_PERM_GOLD)) { header("Location: plus.php?id=3"); exit; } diff --git a/Templates/Plus/8.tpl b/Templates/Plus/8.tpl index 1d4dd119..11c436b9 100644 --- a/Templates/Plus/8.tpl +++ b/Templates/Plus/8.tpl @@ -18,7 +18,11 @@ ## --------------------------------------------------------------------------- ## ################################################################################# -if($session->sit == 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if($session->sitterCan(SITTER_PERM_GOLD)) { $now = time(); $uid = (int)$session->uid; $wid = (int)$village->wid; diff --git a/Templates/Plus/9.tpl b/Templates/Plus/9.tpl index 99e45661..fde23309 100644 --- a/Templates/Plus/9.tpl +++ b/Templates/Plus/9.tpl @@ -18,7 +18,11 @@ ## --------------------------------------------------------------------------- ## ################################################################################# -if($session->sit == 0) { +// PERMISIUNI SITTER: cumparaturile cu aur. +// Inlocuieste vechiul "$session->sit", care citea un flag din tabela online +// scris cu INSERT IGNORE - deci nu se actualiza daca randul exista deja. +// sitterCan() se bazeaza pe sesiune, deci e mereu corect. +if($session->sitterCan(SITTER_PERM_GOLD)) { $uid = (int)$session->uid; $wid = (int)$village->wid; $now = time(); diff --git a/Templates/Plus/getplus.tpl b/Templates/Plus/getplus.tpl index 549e1852..63103175 100644 --- a/Templates/Plus/getplus.tpl +++ b/Templates/Plus/getplus.tpl @@ -46,6 +46,23 @@ if(empty($_POST['plus'])) { sitterCan(SITTER_PERM_GOLD)) { + + die('
' + . (defined('SITTER_P_DENIED') ? SITTER_P_DENIED + : 'Your sitter permissions do not allow this action.') + . '
'); + } + $reward = $_POST['reward'] ?? ''; // whitelist diff --git a/Templates/Profile/account.tpl b/Templates/Profile/account.tpl index cd2c96b8..36d6d206 100644 --- a/Templates/Profile/account.tpl +++ b/Templates/Profile/account.tpl @@ -32,6 +32,48 @@ $sitterError = $form->getError("sit"); $count = 0; if ($session->userinfo['sit1'] != 0) $count += 1; if ($session->userinfo['sit2'] != 0) $count += 1; + +/** + * PERMISIUNI SITTER + * + * Lista e definita o singura data si folosita si la sitterii existenti, si la + * casuta de adaugare, ca sa nu se desincronizeze. Cheile din $_POST sunt + * perm1[], perm2[] si perm_new[], fiecare continand valorile bitilor bifati. + */ +$sitterPermList = array( + SITTER_PERM_ATTACK => defined('SITTER_P_ATTACK') ? SITTER_P_ATTACK : 'send attacks', + SITTER_PERM_RAID => defined('SITTER_P_RAID') ? SITTER_P_RAID : 'send raids', + SITTER_PERM_REINF => defined('SITTER_P_REINF') ? SITTER_P_REINF : 'send reinforcements', + SITTER_PERM_RES => defined('SITTER_P_RES') ? SITTER_P_RES : 'send resources to other players', + SITTER_PERM_GOLD => defined('SITTER_P_GOLD') ? SITTER_P_GOLD : 'spend Gold', +); + +/** + * Casutele pentru un slot. $mask = permisiunile curente, $field = numele + * campului din formular. + */ +function sitterPermBoxes($mask, $field, $list) { + + /** + * Marcaj ascuns: o casuta nebifata nu se trimite deloc prin POST, deci + * "toate debifate" ar arata identic cu "sectiunea nu a fost afisata". + * Campul asta e mereu trimis, asa ca Profile.php poate deosebi cele doua + * situatii si nu reseteaza permisiunile din greseala. + */ + $out = ''; + $out .= '
'; + + foreach ($list as $bit => $label) { + + $checked = (((int) $mask & $bit) === $bit) ? ' checked' : ''; + + $out .= ''; + } + + return $out . '
'; +} ?>
@@ -110,6 +152,19 @@ if (!empty($emailError)) { } ?> + + @@ -133,6 +188,10 @@ if (!empty($emailError)) { (/2) + @@ -160,6 +219,14 @@ foreach ($sitSlots as $type => $key) { echo "\"Remove"; echo ""; echo "".$uname.""; + + // permisiunile curente ale acestui sitter + $permField = 'perm' . $type; + $permMask = isset($session->userinfo[$key . '_perm']) + ? (int) $session->userinfo[$key . '_perm'] + : SITTER_PERM_ALL; + + echo sitterPermBoxes($permMask, $permField, $sitterPermList); echo ""; } } diff --git a/build.php b/build.php index edd990da..513a7cf2 100644 --- a/build.php +++ b/build.php @@ -116,8 +116,15 @@ if ( isset( $_GET['id'] ) ) { if ($session->goldclub == 1 && count($session->villages) > 1) { if (isset($_POST['routeid'])) $routeid = $_POST['routeid']; + /** + * PERMISIUNI SITTER: rutele comerciale costa 2 aur (creare si prelungire). + * Gardul e aici, inaintea ambelor actiuni, nu in fiecare ramura. + */ + $sitterGoldBlocked = (isset($session) && method_exists($session, 'sitterCan') + && !$session->sitterCan(SITTER_PERM_GOLD)); + if (isset($_POST['action']) && $_POST['action'] == 'addRoute') { - if ($session->gold >= 2 && $session->goldclub == 1) { + if ($session->gold >= 2 && $session->goldclub == 1 && !$sitterGoldBlocked) { for ($i = 1; $i <= 4; $i ++) { if (empty($_POST['r'.$i])) $_POST['r'.$i] = 0; } @@ -147,17 +154,20 @@ if ($session->goldclub == 1 && count($session->villages) > 1) { } if (isset($_POST['routeid']) && isset($_POST['action']) && $_POST['action'] == 'extendRoute') { - if ($session->gold >= 2 && $session->goldclub == 1) { + if ($session->gold >= 2 && $session->goldclub == 1 && !$sitterGoldBlocked) { $traderoute = $database->getTradeRouteUid($_POST['routeid']); if ($traderoute == $session->uid) { - $database->editTradeRoute($_POST['routeid'], "timeleft", 604800, 1); - $newgold = $session->gold - 2; - $database->updateUserField($session->uid, 'gold', $newgold, 1); - $session->gold = $newgold; - // Invalidate the 30s session user-cache (see Session::PopulateVar) so - // the gold balance is fresh next request; the write is absolute - // ($session->gold - 2), so a stale cache could double-spend. - unset($_SESSION['cache_user_' . ($_SESSION['username'] ?? '')]); + /** + * spendGold() scade relativ si atomic, apoi invalideaza singur + * cache-ul de sesiune. Inainte se scria absolut pornind de la + * $session->gold, care putea fi vechi de 30 de secunde - exact + * riscul de dubla cheltuire semnalat in comentariul de aici. + * Prelungirea se face DOAR daca plata a reusit. + */ + if ($database->spendGold($session->uid, 2, 'Extend trade route')) { + $database->editTradeRoute($_POST['routeid'], "timeleft", 604800, 1); + $session->gold -= 2; + } } } $route = 1; diff --git a/var/db/struct.sql b/var/db/struct.sql index 240720f4..3d797dda 100644 --- a/var/db/struct.sql +++ b/var/db/struct.sql @@ -1995,7 +1995,9 @@ CREATE TABLE IF NOT EXISTS `%PREFIX%users` ( `b3` int(11) DEFAULT '0', `b4` int(11) DEFAULT '0', `sit1` int(11) DEFAULT '0', + `sit1_perm` int(11) NOT NULL DEFAULT '31', `sit2` int(11) DEFAULT '0', + `sit2_perm` int(11) NOT NULL DEFAULT '31', `alliance` int(11) DEFAULT '0', `alliance_joined` int(11) NOT NULL DEFAULT '0', `sessid` varchar(100) DEFAULT NULL,