smrealms /
smr
We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
| 1 | <?php declare(strict_types=1); |
||
| 2 | |||
| 3 | namespace Smr; |
||
| 4 | |||
| 5 | use Exception; |
||
| 6 | use Smr\Pages\Player\VoteLinkProcessor; |
||
| 7 | |||
| 8 | /** |
||
| 9 | * Site-independent handling of links to external game voting sites. |
||
| 10 | * This is used to award free turns to players for voting. |
||
| 11 | */ |
||
| 12 | class VoteLink { |
||
| 13 | |||
| 14 | public const TIME_BETWEEN_VOTING = 84600; // 23.5 hours |
||
| 15 | |||
| 16 | /** @var ?array<int, int> */ |
||
| 17 | private static ?array $CACHE_TIMEOUTS = null; |
||
| 18 | |||
| 19 | /** @var array<string, mixed> */ |
||
| 20 | public readonly array $data; |
||
| 21 | |||
| 22 | public function __construct( |
||
| 23 | public readonly VoteSite $site, |
||
| 24 | public readonly int $accountID, |
||
| 25 | public readonly int $gameID, |
||
| 26 | ) { |
||
| 27 | $this->data = $site->getData(); |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 28 | } |
||
| 29 | |||
| 30 | public static function clearCache(): void { |
||
| 31 | self::$CACHE_TIMEOUTS = null; |
||
| 32 | } |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Returns the earliest time (in seconds) until free turns |
||
| 36 | * are available across all voting sites. |
||
| 37 | */ |
||
| 38 | public static function getMinTimeUntilFreeTurns(int $accountID, int $gameID): int { |
||
| 39 | $waitTimes = []; |
||
| 40 | foreach (VoteSite::cases() as $site) { |
||
| 41 | $link = new self($site, $accountID, $gameID); |
||
| 42 | if ($link->givesFreeTurns()) { |
||
| 43 | $waitTimes[] = $link->getTimeUntilFreeTurns(); |
||
| 44 | } |
||
| 45 | } |
||
| 46 | if (count($waitTimes) === 0) { |
||
| 47 | throw new Exception('No enabled vote sites give free turns!'); |
||
| 48 | } |
||
| 49 | return min($waitTimes); |
||
| 50 | } |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Does this VoteSite have a voting callback that can be used |
||
| 54 | * to award free turns? |
||
| 55 | */ |
||
| 56 | public function givesFreeTurns(): bool { |
||
| 57 | return isset($this->data['img_star']); |
||
| 58 | } |
||
| 59 | |||
| 60 | /** |
||
| 61 | * Time until the account can get free turns from voting at this site. |
||
| 62 | * If the time is 0, this site is eligible for free turns. |
||
| 63 | */ |
||
| 64 | public function getTimeUntilFreeTurns(bool $forceUpdate = false): int { |
||
| 65 | if (!$this->givesFreeTurns()) { |
||
| 66 | throw new Exception('This vote site cannot award free turns!'); |
||
| 67 | } |
||
| 68 | |||
| 69 | // Populate timeout cache from the database |
||
| 70 | if ($forceUpdate || !isset(self::$CACHE_TIMEOUTS)) { |
||
| 71 | self::$CACHE_TIMEOUTS = []; // ensure this is set |
||
| 72 | $db = Database::getInstance(); |
||
| 73 | $dbResult = $db->read('SELECT link_id, timeout FROM vote_links WHERE account_id=' . $db->escapeNumber($this->accountID)); |
||
| 74 | foreach ($dbResult->records() as $dbRecord) { |
||
| 75 | // 'timeout' is the last time the player claimed free turns (or 0, if unclaimed) |
||
| 76 | self::$CACHE_TIMEOUTS[$dbRecord->getInt('link_id')] = $dbRecord->getInt('timeout'); |
||
| 77 | } |
||
| 78 | } |
||
| 79 | |||
| 80 | // If not in the vote_link database, this site is eligible now. |
||
| 81 | $lastClaimTime = self::$CACHE_TIMEOUTS[$this->site->value] ?? 0; |
||
| 82 | return max(0, $lastClaimTime + self::TIME_BETWEEN_VOTING - Epoch::time()); |
||
| 83 | } |
||
| 84 | |||
| 85 | /** |
||
| 86 | * Register that the player has clicked on a vote site that is eligible |
||
| 87 | * for free turns, so that we will accept incoming votes. This ensures |
||
| 88 | * that voting is done through an authenticated SMR session. |
||
| 89 | */ |
||
| 90 | public function setClicked(): void { |
||
| 91 | $db = Database::getInstance(); |
||
| 92 | $db->lockTable('vote_links'); |
||
| 93 | try { |
||
| 94 | if (!$this->freeTurnsReady(true)) { |
||
| 95 | // Player has clicked a free turns link after already getting |
||
| 96 | // free turns. This should not occur naturally. |
||
| 97 | throw new Exception('Account ID ' . $this->accountID . ' attempted vote link abuse'); |
||
| 98 | } |
||
| 99 | |||
| 100 | // Don't start the timeout until the vote actually goes through. |
||
| 101 | $db->replace('vote_links', [ |
||
| 102 | 'account_id' => $db->escapeNumber($this->accountID), |
||
| 103 | 'link_id' => $db->escapeNumber($this->site->value), |
||
| 104 | 'timeout' => $db->escapeNumber(0), |
||
| 105 | 'turns_claimed' => $db->escapeBoolean(false), |
||
| 106 | ]); |
||
| 107 | } finally { |
||
| 108 | $db->unlock(); |
||
| 109 | } |
||
| 110 | } |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Register that the player has been awarded their free turns. |
||
| 114 | * |
||
| 115 | * @return bool True if account was eligible for free turns (i.e. in the setClicked state). |
||
| 116 | */ |
||
| 117 | public function setFreeTurnsAwarded(): bool { |
||
| 118 | $db = Database::getInstance(); |
||
| 119 | $db->write('UPDATE vote_links SET timeout = ' . Epoch::time() . ', turns_claimed = ' . $db->escapeBoolean(true) . ' WHERE account_id = ' . $db->escapeNumber($this->accountID) . ' AND link_id = ' . $db->escapeNumber($this->site->value) . ' AND timeout = 0 AND turns_claimed = ' . $db->escapeBoolean(false)); |
||
| 120 | return $db->getChangedRows() === 1; |
||
| 121 | } |
||
| 122 | |||
| 123 | /** |
||
| 124 | * Returns true if account can currently receive free turns at this site. |
||
| 125 | */ |
||
| 126 | public function freeTurnsReady(bool $forceUpdate = false): bool { |
||
| 127 | return $this->givesFreeTurns() && $this->gameID != 0 && $this->getTimeUntilFreeTurns($forceUpdate) <= 0; |
||
| 128 | } |
||
| 129 | |||
| 130 | /** |
||
| 131 | * Returns the image to display for this voting site. |
||
| 132 | */ |
||
| 133 | public function getImg(): string { |
||
| 134 | if (!$this->freeTurnsReady()) { |
||
| 135 | return $this->data['img_default']; |
||
| 136 | } |
||
| 137 | return $this->data['img_star']; |
||
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Returns the URL that should be used for this voting site. |
||
| 142 | */ |
||
| 143 | public function getUrl(): string { |
||
| 144 | if (!$this->freeTurnsReady()) { |
||
| 145 | return $this->data['url_base']; |
||
| 146 | } |
||
| 147 | return $this->data['url_func']($this->data['url_base'], $this->accountID, $this->gameID); |
||
| 148 | } |
||
| 149 | |||
| 150 | /** |
||
| 151 | * Returns the SN to redirect the current page to if free turns are |
||
| 152 | * available; otherwise, returns false. |
||
| 153 | */ |
||
| 154 | public function getSN(): string|false { |
||
| 155 | if (!$this->freeTurnsReady()) { |
||
| 156 | return false; |
||
| 157 | } |
||
| 158 | // This page will prepare the account for the voting callback. |
||
| 159 | return (new VoteLinkProcessor($this->site))->href(); |
||
| 160 | } |
||
| 161 | |||
| 162 | } |
||
| 163 |