<?php namespace ManiaControl\Players; use ManiaControl\ManiaControl; /** * Player Data Manager * * @author ManiaControl Team <mail@maniacontrol.com> * @copyright 2014 ManiaControl Team * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 */ class PlayerDataManager { const TABLE_PLAYERDATAMETADATA = 'mc_playerdata_metadata'; const TABLE_PLAYERDATA = 'mc_playerdata'; const TYPE_STRING = 'string'; const TYPE_INT = 'int'; const TYPE_REAL = 'real'; const TYPE_BOOL = 'bool'; const TYPE_ARRAY = 'array'; /* * Private Properties */ private $maniaControl = null; private $arrayDelimiter = ';;'; private $metaData = array(); private $storedData = array(); /** * Construct player manager * * @param \ManiaControl\ManiaControl $maniaControl */ public function __construct(ManiaControl $maniaControl) { $this->maniaControl = $maniaControl; $this->initTables(); // Store Stats MetaData $this->storeMetaData(); } /** * Initialize necessary database tables * * @return bool */ private function initTables() { $mysqli = $this->maniaControl->database->mysqli; $defaultType = "'" . self::TYPE_STRING . "'"; $typeSet = $defaultType . ",'" . self::TYPE_INT . "','" . self::TYPE_REAL . "','" . self::TYPE_BOOL . "','" . self::TYPE_ARRAY . "'"; $query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_PLAYERDATAMETADATA . "` ( `dataId` int(11) NOT NULL AUTO_INCREMENT, `class` varchar(100) NOT NULL, `dataName` varchar(100) NOT NULL, `type` set({$typeSet}) NOT NULL DEFAULT {$defaultType}, `defaultValue` varchar(150) NOT NULL, `description` varchar(150) NOT NULL, PRIMARY KEY (`dataId`), UNIQUE KEY `name` (`class`, `dataName`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Player-Data MetaData' AUTO_INCREMENT=1;"; $statement = $mysqli->prepare($query); if ($mysqli->error) { trigger_error($mysqli->error, E_USER_ERROR); return false; } $statement->execute(); if ($statement->error) { trigger_error($statement->error, E_USER_ERROR); return false; } $statement->close(); $query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_PLAYERDATA . "` ( `index` int(11) NOT NULL AUTO_INCREMENT, `serverIndex` int(11) NOT NULL, `playerId` int(11) NOT NULL, `dataId` int(11) NOT NULL, `value` varchar(150) NOT NULL, `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`index`), UNIQUE KEY `unique` (`dataId`,`playerId`,`serverIndex`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Player Data' AUTO_INCREMENT=1;"; $statement = $mysqli->prepare($query); if ($mysqli->error) { trigger_error($mysqli->error, E_USER_ERROR); return false; } $statement->execute(); if ($statement->error) { trigger_error($statement->error, E_USER_ERROR); return false; } $statement->close(); return true; } /** * Store Meta Data from the Database in the Ram */ private function storeMetaData() { $mysqli = $this->maniaControl->database->mysqli; $query = "SELECT * FROM `" . self::TABLE_PLAYERDATAMETADATA . "`;"; $result = $mysqli->query($query); if (!$result) { trigger_error($mysqli->error); return; } while ($row = $result->fetch_object()) { $this->metaData[$row->class . $row->dataName] = $row; } $result->close(); } /** * Destroys the stored PlayerData (Method get called by PlayerManager, so don't call it anywhere else) * * @param Player $player */ public function destroyPlayerData(Player $player) { unset($this->storedData[$player->index]); } /** * Defines the Player-Data MetaData * * @param $object * @param $dataName * @param $default * @param $dataDescription (optional) * @return bool */ public function defineMetaData($object, $dataName, $default, $dataDescription = '') { $mysqli = $this->maniaControl->database->mysqli; $className = $this->getClassName($object); $query = "INSERT INTO `" . self::TABLE_PLAYERDATAMETADATA . "` ( `class`, `dataName`, `type`, `defaultValue`, `description` ) VALUES ( ?, ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE `type` = VALUES(`type`), `defaultValue` = VALUES(`defaultValue`), `description` = VALUES(`description`);"; $statement = $mysqli->prepare($query); if ($mysqli->error) { trigger_error($mysqli->error); return false; } $type = $this->getType($default); $statement->bind_param('sssss', $className, $dataName, $type, $default, $dataDescription); $statement->execute(); if ($statement->error) { trigger_error($statement->error); $statement->close(); return false; } $statement->close(); return true; } /** * Get Class Name of a Parameter * * @param mixed $param * @return string */ private function getClassName($param) { if (is_object($param)) { return get_class($param); } if (is_string($param)) { return $param; } trigger_error('Invalid class param. ' . $param); return (string)$param; } /** * Get Type of a Parameter * * @param mixed $param * @return string */ private function getType($param) { if (is_int($param)) { return self::TYPE_INT; } if (is_real($param)) { return self::TYPE_REAL; } if (is_bool($param)) { return self::TYPE_BOOL; } if (is_string($param)) { return self::TYPE_STRING; } if (is_array($param)) { return self::TYPE_ARRAY; } trigger_error('Unsupported setting type. ' . print_r($param, true)); return null; } /** * Gets the Player Data * * @param mixed $object * @param string $dataName * @param Player $player * @param int $serverIndex * @return mixed|null */ public function getPlayerData($object, $dataName, Player $player, $serverIndex = -1) { $className = $this->getClassName($object); $meta = $this->metaData[$className . $dataName]; //Check if data is already in the ram if (isset($this->storedData[$player->index])) { if (isset($this->storedData[$player->index][$meta->dataId])) { return $this->storedData[$player->index][$meta->dataId]; } } $mysqli = $this->maniaControl->database->mysqli; $dataQuery = "SELECT `value` FROM `" . self::TABLE_PLAYERDATA . "` WHERE `dataId` = ? AND `playerId` = ? AND `serverIndex` = ?;"; $dataStatement = $mysqli->prepare($dataQuery); if ($mysqli->error) { trigger_error($mysqli->error); return null; } $dataStatement->bind_param('iii', $meta->dataId, $player->index, $serverIndex); $dataStatement->execute(); if ($dataStatement->error) { trigger_error($dataStatement->error); return null; } $dataStatement->store_result(); if ($dataStatement->num_rows <= 0) { $this->setPlayerData($object, $dataName, $player, $meta->defaultValue, $serverIndex); return $meta->default; } $dataStatement->bind_result($value); $dataStatement->fetch(); $dataStatement->free_result(); $dataStatement->close(); $data = $this->castSetting($meta->type, $value); //Store setting in the ram if (!isset($this->storedData[$player->index])) { $this->storedData[$player->index] = array(); } $this->storedData[$player->index][$meta->dataId] = $data; return $data; } /** * Set a PlayerData to a specific defined statMetaData * * @param mixed $object * @param string $dataName * @param Player $player * @param mixed $value * @param int $serverIndex (let it empty if its global) * @return bool */ public function setPlayerData($object, $dataName, Player $player, $value, $serverIndex = -1) { $className = $this->getClassName($object); if (!$player) { return false; } $dataId = $this->getMetaDataId($className, $dataName); if (!$dataId) { return false; } $mysqli = $this->maniaControl->database->mysqli; $query = "INSERT INTO `" . self::TABLE_PLAYERDATA . "` ( `serverIndex`, `playerId`, `dataId`, `value` ) VALUES ( ?, ?, ?, ? ) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"; $statement = $mysqli->prepare($query); if ($mysqli->error) { trigger_error($mysqli->error); return false; } $statement->bind_param('iiis', $serverIndex, $player->index, $dataId, $value); $statement->execute(); if ($statement->error) { trigger_error($statement->error); $statement->close(); return false; } $statement->close(); //FIXME store changed value if (isset($this->storedData[$player->index]) && isset($this->storedData[$player->index][$dataId])) { unset($this->storedData[$player->index][$dataId]); } return true; } /** * Return the Id of the MetaData * * @param string $className * @param string $statName * @return int */ private function getMetaDataId($className, $statName) { if (isset($this->metaData[$className . $statName])) { $stat = $this->metaData[$className . $statName]; return (int)$stat->dataId; } return null; } /** * Cast a Setting to the given Type * * @param string $type * @param mixed $value * @return mixed */ private function castSetting($type, $value) { if ($type === self::TYPE_INT) { return (int)$value; } if ($type === self::TYPE_REAL) { return (float)$value; } if ($type === self::TYPE_BOOL) { return (bool)$value; } if ($type === self::TYPE_STRING) { return (string)$value; } if ($type === self::TYPE_ARRAY) { return explode($this->arrayDelimiter, $value); } trigger_error('Unsupported setting type. ' . print_r($type, true)); return $value; } }