diff --git a/src/Cli/AddUserCommand.php b/src/Cli/AddUserCommand.php index 45fb57d..90f3ba3 100644 --- a/src/Cli/AddUserCommand.php +++ b/src/Cli/AddUserCommand.php @@ -1,7 +1,7 @@ $input->getArgument('email'), - ]); - + $user = new User(); + $user->setEmail($input->getArgument('email')); $user->setPlainPassword($input->getArgument('password')); if ($input->hasArgument('type')) { @@ -31,9 +29,8 @@ class AddUserCommand extends Command } try { - $modify = new Modify(\Container::$dbConnection, 'users'); - $modify->fill($user->toArray()); - $modify->save(); + $pdm = new PersistentDataManager(); + $pdm->saveToDb($user); } catch (\Exception $e) { $output->writeln('Adding user failed!'); $output->writeln(''); diff --git a/src/Controller/GameController.php b/src/Controller/GameController.php index c9472d9..0027e2c 100644 --- a/src/Controller/GameController.php +++ b/src/Controller/GameController.php @@ -1,7 +1,6 @@ mapRepository->getById($mapId); - $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']); - $session = $this->request->session(); if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) { $session->set('state', [ 'mapId' => $mapId, - 'area' => $map['area'], + 'area' => $map->getArea(), 'rounds' => [] ]); } - return ['mapId' => $mapId, 'mapName' => $map['name'], 'bounds' => $bounds->toArray()]; + return ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()]; } } diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index 913cfa6..b15ffa7 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -4,6 +4,7 @@ use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Util\Geo\Position; use MapGuesser\Response\JsonContent; use MapGuesser\Interfaces\Response\IContent; +use MapGuesser\PersistentData\Model\Place; use MapGuesser\Repository\PlaceRepository; class GameFlowController @@ -33,11 +34,14 @@ class GameFlowController } if (count($state['rounds']) === 0) { - $place = $this->placeRepository->getForMapWithValidPano($mapId); - $state['rounds'][] = $place; + $placesWithoutPano = []; + $place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano); + + $this->addNewRoundToState($state, $place, $placesWithoutPano); + $session->set('state', $state); - $data = ['panoId' => $place['panoId']]; + $data = ['panoId' => $place->getPanoIdCached()]; } else { $rounds = count($state['rounds']); $last = $state['rounds'][$rounds - 1]; @@ -93,18 +97,20 @@ class GameFlowController $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]); } - $place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude); - $state['rounds'][] = $place; - $session->set('state', $state); + $placesWithoutPano = []; + $place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano); - $panoId = $place['panoId']; + $this->addNewRoundToState($state, $place, $placesWithoutPano); + + $panoId = $place->getPanoIdCached(); } else { $state['rounds'] = []; - $session->set('state', $state); $panoId = null; } + $session->set('state', $state); + $data = [ 'result' => [ 'position' => $position->toArray(), @@ -116,6 +122,16 @@ class GameFlowController return new JsonContent($data); } + private function addNewRoundToState(&$state, Place $place, array $placesWithoutPano): void + { + $state['rounds'][] = [ + 'placesWithoutPano' => $placesWithoutPano, + 'placeId' => $place->getId(), + 'position' => $place->getPosition(), + 'panoId' => $place->getPanoIdCached() + ]; + } + private function calculateDistance(Position $realPosition, Position $guessPosition): float { return $realPosition->calculateDistanceTo($guessPosition); diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php index a8227af..f83633d 100644 --- a/src/Controller/LoginController.php +++ b/src/Controller/LoginController.php @@ -1,11 +1,9 @@ request = $request; + $this->userRepository = new UserRepository(); } public function getLoginForm() { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } @@ -33,26 +32,18 @@ class LoginController public function login(): IContent { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { $data = ['success' => true]; return new JsonContent($data); } - $select = new Select(\Container::$dbConnection, 'users'); - $select->columns(User::getFields()); - $select->where('email', '=', $this->request->post('email')); + $user = $this->userRepository->getByEmail($this->request->post('email')); - $userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - - if ($userData === null) { + if ($user === null) { $data = ['error' => 'user_not_found']; return new JsonContent($data); } - $user = new User($userData); - if (!$user->getActive()) { $data = ['error' => 'user_not_active']; return new JsonContent($data); @@ -63,7 +54,7 @@ class LoginController return new JsonContent($data); } - $session->set('user', $user); + $this->request->setUser($user); $data = ['success' => true]; return new JsonContent($data); @@ -71,7 +62,7 @@ class LoginController public function logout(): IRedirect { - $this->request->session()->delete('user'); + $this->request->setUser(null); return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } diff --git a/src/Controller/MapAdminController.php b/src/Controller/MapAdminController.php index 075017a..d12ccbf 100644 --- a/src/Controller/MapAdminController.php +++ b/src/Controller/MapAdminController.php @@ -1,13 +1,15 @@ request = $request; + $this->pdm = new PersistentDataManager(); $this->mapRepository = new MapRepository(); $this->placeRepository = new PlaceRepository(); } @@ -45,18 +50,14 @@ class MapAdminController implements ISecured if ($mapId) { $map = $this->mapRepository->getById($mapId); - $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']); - $places = $this->getPlaces($mapId); + $places = $this->getPlaces($map); } else { - $map = [ - 'name' => self::$unnamedMapName, - 'description' => '' - ]; - $bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0); + $map = new Map(); + $map->setName(self::$unnamedMapName); $places = []; } - $data = ['mapId' => $mapId, 'mapName' => $map['name'], 'mapDescription' => str_replace('
', "\n", $map['description']), 'bounds' => $bounds->toArray(), 'places' => &$places]; + $data = ['mapId' => $mapId, 'mapName' => $map->getName(), 'mapDescription' => str_replace('
', "\n", $map->getDescription()), 'bounds' => $map->getBounds()->toArray(), 'places' => &$places]; return new HtmlContent('admin/map_editor', $data); } @@ -64,9 +65,9 @@ class MapAdminController implements ISecured { $placeId = (int) $this->request->query('placeId'); - $placeData = $this->placeRepository->getById($placeId); + $place = $this->placeRepository->getById($placeId); - $data = ['panoId' => $placeData['panoId']]; + $data = ['panoId' => $place->getFreshPanoId()]; return new JsonContent($data); } @@ -76,8 +77,12 @@ class MapAdminController implements ISecured \Container::$dbConnection->startTransaction(); - if (!$mapId) { - $mapId = $this->addNewMap(); + if ($mapId) { + $map = $this->mapRepository->getById($mapId); + } else { + $map = new Map(); + $map->setName(self::$unnamedMapName); + $this->pdm->saveToDb($map); } if (isset($_POST['added'])) { @@ -85,11 +90,18 @@ class MapAdminController implements ISecured foreach ($_POST['added'] as $placeRaw) { $placeRaw = json_decode($placeRaw, true); - $addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $this->placeRepository->addToMap($mapId, [ - 'lat' => (float) $placeRaw['lat'], - 'lng' => (float) $placeRaw['lng'], - 'pano_id_cached_timestamp' => $placeRaw['panoId'] === -1 ? (new DateTime('-1 day'))->format('Y-m-d H:i:s') : null - ])]; + $place = new Place(); + $place->setMap($map); + $place->setLat((float) $placeRaw['lat']); + $place->setLng((float) $placeRaw['lng']); + + if ($placeRaw['panoId'] === -1) { + $place->setPanoIdCachedTimestampDate(new DateTime('-1 day')); + } + + $this->pdm->saveToDb($place); + + $addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()]; } } else { $addedIds = []; @@ -99,10 +111,12 @@ class MapAdminController implements ISecured foreach ($_POST['edited'] as $placeRaw) { $placeRaw = json_decode($placeRaw, true); - $this->placeRepository->modify((int) $placeRaw['id'], [ - 'lat' => (float) $placeRaw['lat'], - 'lng' => (float) $placeRaw['lng'] - ]); + $place = $this->placeRepository->getById((int) $placeRaw['id']); + $place->setLat((float) $placeRaw['lat']); + $place->setLng((float) $placeRaw['lng']); + $place->setPanoIdCachedTimestampDate(new DateTime('-1 day')); + + $this->pdm->saveToDb($place); } } @@ -110,45 +124,42 @@ class MapAdminController implements ISecured foreach ($_POST['deleted'] as $placeRaw) { $placeRaw = json_decode($placeRaw, true); - $this->placeRepository->delete($placeRaw['id']); + $place = $this->placeRepository->getById((int) $placeRaw['id']); + + $this->pdm->deleteFromDb($place); } } - $mapBounds = $this->calculateMapBounds($mapId); + $mapBounds = $this->calculateMapBounds($map); - $map = [ - 'bound_south_lat' => $mapBounds->getSouthLat(), - 'bound_west_lng' => $mapBounds->getWestLng(), - 'bound_north_lat' => $mapBounds->getNorthLat(), - 'bound_east_lng' => $mapBounds->getEastLng(), - 'area' => $mapBounds->calculateApproximateArea(), - ]; + $map->setBounds($mapBounds); + $map->setArea($mapBounds->calculateApproximateArea()); if (isset($_POST['name'])) { - $map['name'] = $_POST['name'] ? $_POST['name'] : self::$unnamedMapName; + $map->setName($_POST['name'] ? $_POST['name'] : self::$unnamedMapName); } if (isset($_POST['description'])) { - $map['description'] = str_replace(["\n", "\r\n"], '
', $_POST['description']); + $map->setDescription(str_replace(["\n", "\r\n"], '
', $_POST['description'])); } - $this->saveMapData($mapId, $map); + $this->pdm->saveToDb($map); \Container::$dbConnection->commit(); - $data = ['mapId' => $mapId, 'added' => $addedIds]; + $data = ['mapId' => $map->getId(), 'added' => $addedIds]; return new JsonContent($data); } public function deleteMap() { $mapId = (int) $this->request->query('mapId'); + $map = $this->mapRepository->getById($mapId); + \Container::$dbConnection->startTransaction(); - $this->deletePlaces($mapId); + $this->deletePlaces($map); - $modify = new Modify(\Container::$dbConnection, 'maps'); - $modify->setId($mapId); - $modify->delete(); + $this->pdm->deleteFromDb($map); \Container::$dbConnection->commit(); @@ -156,26 +167,30 @@ class MapAdminController implements ISecured return new JsonContent($data); } - private function deletePlaces(int $mapId): void + private function deletePlaces(Map $map): void { + //TODO: relations? $select = new Select(\Container::$dbConnection, 'places'); - $select->columns(['id']); - $select->where('map_id', '=', $mapId); + $select->columns(Place::getFields()); + $select->where('map_id', '=', $map->getId()); $result = $select->execute(); - while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) { - $modify = new Modify(\Container::$dbConnection, 'places'); - $modify->setId($place['id']); - $modify->delete(); + while ($placeData = $result->fetch(IResultSet::FETCH_ASSOC)) { + $place = new Place(); + + $this->pdm->fillWithData($placeData, $place); + + $this->pdm->deleteFromDb($place); } } - private function calculateMapBounds(int $mapId): Bounds + private function calculateMapBounds(Map $map): Bounds { + //TODO: from repository or relations $select = new Select(\Container::$dbConnection, 'places'); $select->columns(['lat', 'lng']); - $select->where('map_id', '=', $mapId); + $select->where('map_id', '=', $map->getId()); $result = $select->execute(); @@ -187,35 +202,12 @@ class MapAdminController implements ISecured return $bounds; } - private function addNewMap(): int - { - $modify = new Modify(\Container::$dbConnection, 'maps'); - $modify->fill([ - 'name' => self::$unnamedMapName, - 'description' => '', - 'bound_south_lat' => 0.0, - 'bound_west_lng' => 0.0, - 'bound_north_lat' => 0.0, - 'bound_east_lng' => 0.0 - ]); - $modify->save(); - - return $modify->getId(); - } - - private function saveMapData(int $mapId, array $map): void - { - $modify = new Modify(\Container::$dbConnection, 'maps'); - $modify->setId($mapId); - $modify->fill($map); - $modify->save(); - } - - private function &getPlaces(int $mapId): array + private function &getPlaces(Map $map): array { + //TODO: from repository or relations $select = new Select(\Container::$dbConnection, 'places'); $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']); - $select->where('map_id', '=', $mapId); + $select->where('map_id', '=', $map->getId()); $result = $select->execute(); diff --git a/src/Controller/MapsController.php b/src/Controller/MapsController.php index 0546ec7..932e69e 100644 --- a/src/Controller/MapsController.php +++ b/src/Controller/MapsController.php @@ -19,6 +19,7 @@ class MapsController public function getMaps(): IContent { + //TODO: from repository $select = new Select(\Container::$dbConnection, 'maps'); $select->columns([ ['maps', 'id'], diff --git a/src/Controller/SignupController.php b/src/Controller/SignupController.php index b50bfe2..77b3197 100644 --- a/src/Controller/SignupController.php +++ b/src/Controller/SignupController.php @@ -1,13 +1,14 @@ request = $request; + $this->pdm = new PersistentDataManager(); + $this->userRepository = new UserRepository(); + $this->userConfirmationRepository = new UserConfirmationRepository(); } public function getSignupForm() { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } @@ -35,9 +43,7 @@ class SignupController public function signup(): IContent { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { //TODO: return with some error $data = ['success' => true]; return new JsonContent($data); @@ -48,15 +54,9 @@ class SignupController return new JsonContent($data); } - $select = new Select(\Container::$dbConnection, 'users'); - $select->columns(User::getFields()); - $select->where('email', '=', $this->request->post('email')); - - $userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - - if ($userData !== null) { - $user = new User($userData); + $user = $this->userRepository->getByEmail($this->request->post('email')); + if ($user !== null) { if ($user->getActive()) { $data = ['error' => 'user_found']; } else { @@ -75,25 +75,21 @@ class SignupController return new JsonContent($data); } - $user = new User([ - 'email' => $this->request->post('email'), - ]); - + $user = new User(); + $user->setEmail($this->request->post('email')); $user->setPlainPassword($this->request->post('password')); \Container::$dbConnection->startTransaction(); - $modify = new Modify(\Container::$dbConnection, 'users'); - $modify->fill($user->toArray()); - $modify->save(); - $userId = $modify->getId(); + $this->pdm->saveToDb($user); $token = hash('sha256', serialize($user) . random_bytes(10) . microtime()); - $modify = new Modify(\Container::$dbConnection, 'user_confirmations'); - $modify->set('user_id', $userId); - $modify->set('token', $token); - $modify->save(); + $confirmation = new UserConfirmation(); + $confirmation->setUser($user); + $confirmation->setToken($token); + + $this->pdm->saveToDb($confirmation); \Container::$dbConnection->commit(); @@ -105,17 +101,11 @@ class SignupController public function activate() { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } - $select = new Select(\Container::$dbConnection, 'user_confirmations'); - $select->columns(['id', 'user_id']); - $select->where('token', '=', $this->request->query('token')); - - $confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC); + $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token')); if ($confirmation === null) { $data = []; @@ -124,42 +114,27 @@ class SignupController \Container::$dbConnection->startTransaction(); - $modify = new Modify(\Container::$dbConnection, 'user_confirmations'); - $modify->setId($confirmation['id']); - $modify->delete(); + $this->pdm->deleteFromDb($confirmation); - $modify = new Modify(\Container::$dbConnection, 'users'); - $modify->setId($confirmation['user_id']); - $modify->set('active', true); - $modify->save(); + $user = $this->userRepository->getById($confirmation->getUserId()); + $user->setActive(true); + + $this->pdm->saveToDb($user); \Container::$dbConnection->commit(); - $select = new Select(\Container::$dbConnection, 'users'); - $select->columns(User::getFields()); - $select->whereId($confirmation['user_id']); - - $userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - $user = new User($userData); - - $session->set('user', $user); + $this->request->setUser($user); return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } public function cancel() { - $session = $this->request->session(); - - if ($session->get('user')) { + if ($this->request->user() !== null) { return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY); } - $select = new Select(\Container::$dbConnection, 'user_confirmations'); - $select->columns(['id', 'user_id']); - $select->where('token', '=', $this->request->query('token')); - - $confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC); + $confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token')); if ($confirmation === null) { $data = ['success' => false]; @@ -168,13 +143,11 @@ class SignupController \Container::$dbConnection->startTransaction(); - $modify = new Modify(\Container::$dbConnection, 'user_confirmations'); - $modify->setId($confirmation['id']); - $modify->delete(); + $this->pdm->deleteFromDb($confirmation); - $modify = new Modify(\Container::$dbConnection, 'users'); - $modify->setId($confirmation['user_id']); - $modify->delete(); + $user = $this->userRepository->getById($confirmation->getUserId()); + + $this->pdm->deleteFromDb($user); \Container::$dbConnection->commit(); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index f60f809..778217a 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -1,9 +1,10 @@ request = $request; + $this->pdm = new PersistentDataManager(); } public function authorize(): bool @@ -25,6 +29,9 @@ class UserController implements ISecured public function getProfile(): IContent { + /** + * @var User $user + */ $user = $this->request->user(); $data = ['user' => $user->toArray()]; @@ -33,6 +40,9 @@ class UserController implements ISecured public function saveProfile(): IContent { + /** + * @var User $user + */ $user = $this->request->user(); if (!$user->checkPassword($this->request->post('password'))) { @@ -54,11 +64,7 @@ class UserController implements ISecured $user->setPlainPassword($this->request->post('password_new')); } - $modify = new Modify(\Container::$dbConnection, 'users'); - $modify->fill($user->toArray()); - $modify->save(); - - $this->request->session()->set('user', $user); + $this->pdm->saveToDb($user); $data = ['success' => true]; return new JsonContent($data); diff --git a/src/Database/Query/Modify.php b/src/Database/Query/Modify.php index 5554409..b0a6f82 100755 --- a/src/Database/Query/Modify.php +++ b/src/Database/Query/Modify.php @@ -1,7 +1,6 @@ generateDiff(); + $attributes = $this->attributes; + unset($attributes[$this->idName]); - if (count($diff) === 0) { - return; - }*/ - - $diff = $this->attributes; - unset($diff[$this->idName]); - - $set = $this->generateColumnsWithBinding(array_keys($diff)); + $set = $this->generateColumnsWithBinding(array_keys($attributes)); $query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?'; $stmt = $this->connection->prepare($query); - $stmt->execute(array_merge($diff, [$this->idName => $this->attributes[$this->idName]])); - } - - private function readFromDB(array $columns): void - { - $select = (new Select($this->connection, $this->table)) - ->setIdName($this->idName) - ->whereId($this->attributes[$this->idName]) - ->columns($columns); - - $this->original = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - } - - private function generateDiff(): array - { - $this->readFromDB(array_keys($this->attributes)); - - $diff = []; - - foreach ($this->attributes as $name => $value) { - $original = $this->original[$name]; - - if ($original != $value) { - $diff[$name] = $value; - } - } - - return $diff; + $stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]])); } public static function generateColumnsWithBinding(array $columns): string diff --git a/src/Database/Query/Select.php b/src/Database/Query/Select.php index a379728..a2ffda6 100644 --- a/src/Database/Query/Select.php +++ b/src/Database/Query/Select.php @@ -32,10 +32,13 @@ class Select private array $limit; - public function __construct(IConnection $connection, string $table) + public function __construct(IConnection $connection, ?string $table = null) { $this->connection = $connection; - $this->table = $table; + + if ($table !== null) { + $this->table = $table; + } } public function setIdName(string $idName): Select @@ -52,6 +55,13 @@ class Select return $this; } + public function from(string $table): Select + { + $this->table = $table; + + return $this; + } + public function columns(array $columns): Select { $this->columns = array_merge($this->columns, $columns); diff --git a/src/Interfaces/Authentication/IUser.php b/src/Interfaces/Authentication/IUser.php index f95824c..b5f0182 100644 --- a/src/Interfaces/Authentication/IUser.php +++ b/src/Interfaces/Authentication/IUser.php @@ -8,5 +8,9 @@ interface IUser public function hasPermission(int $permission): bool; + public function getUniqueId(); + public function getDisplayName(): string; + + public function checkPassword(string $password): bool; } diff --git a/src/Interfaces/Request/IRequest.php b/src/Interfaces/Request/IRequest.php index 351550f..e4499e5 100644 --- a/src/Interfaces/Request/IRequest.php +++ b/src/Interfaces/Request/IRequest.php @@ -14,5 +14,7 @@ interface IRequest public function session(): ISession; + public function setUser(?IUser $user): void; + public function user(): ?IUser; } diff --git a/src/Model/BaseModel.php b/src/Model/BaseModel.php deleted file mode 100644 index 48713f7..0000000 --- a/src/Model/BaseModel.php +++ /dev/null @@ -1,49 +0,0 @@ - $value) { - $method = 'set' . str_replace('_', '', ucwords($key, '_')); - - if (method_exists($this, $method)) { - $this->$method($value); - } - } - } - - public function setId($id): void - { - $this->id = $id; - } - - public function getId() - { - return $this->id; - } - - function toArray(): array - { - $array = []; - - foreach (self::getFields() as $key) { - $method = 'get' . str_replace('_', '', ucwords($key, '_')); - - if (method_exists($this, $method)) { - $array[$key] = $this->$method(); - } - } - - return $array; - } -} diff --git a/src/PersistentData/Model/Map.php b/src/PersistentData/Model/Map.php new file mode 100644 index 0000000..919c156 --- /dev/null +++ b/src/PersistentData/Model/Map.php @@ -0,0 +1,103 @@ +bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0); + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function setBounds(Bounds $bounds): void + { + $this->bounds = $bounds; + } + + public function setBoundSouthLat(float $bound_south_lat): void + { + $this->bounds->setSouthLat($bound_south_lat); + } + + public function setBoundWestLng(float $bound_west_lng): void + { + $this->bounds->setWestLng($bound_west_lng); + } + + public function setBoundNorthLat(float $bound_north_lat): void + { + $this->bounds->setNorthLat($bound_north_lat); + } + + public function setBoundEastLng(float $bound_east_lng): void + { + $this->bounds->setEastLng($bound_east_lng); + } + + public function setArea(float $area): void + { + $this->area = $area; + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getBounds(): Bounds + { + return $this->bounds; + } + + public function getBoundSouthLat(): float + { + return $this->bounds->getSouthLat(); + } + + public function getBoundWestLng(): float + { + return $this->bounds->getWestLng(); + } + + public function getBoundNorthLat(): float + { + return $this->bounds->getNorthLat(); + } + + public function getBoundEastLng(): float + { + return $this->bounds->getEastLng(); + } + + public function getArea(): float + { + return $this->area; + } +} diff --git a/src/PersistentData/Model/Model.php b/src/PersistentData/Model/Model.php new file mode 100644 index 0000000..de4c483 --- /dev/null +++ b/src/PersistentData/Model/Model.php @@ -0,0 +1,69 @@ +id = $id; + } + + public function getId() + { + return $this->id; + } + + public function toArray(): array + { + $array = []; + + foreach (self::getFields() as $key) { + $method = 'get' . str_replace('_', '', ucwords($key, '_')); + + if (method_exists($this, $method)) { + $array[$key] = $this->$method(); + } + } + + return $array; + } + + public function saveSnapshot(): void + { + $this->snapshot = $this->toArray(); + } + + public function resetSnapshot(): void + { + $this->snapshot = []; + } + + public function getSnapshot(): array + { + return $this->snapshot; + } +} diff --git a/src/PersistentData/Model/Place.php b/src/PersistentData/Model/Place.php new file mode 100644 index 0000000..d8d26b1 --- /dev/null +++ b/src/PersistentData/Model/Place.php @@ -0,0 +1,150 @@ + Map::class]; + + private ?Map $map = null; + + private ?int $mapId = null; + + private Position $position; + + private ?string $panoIdCached = null; + + private ?DateTime $panoIdCachedTimestamp = null; + + public function __construct() + { + $this->position = new Position(0.0, 0.0); + } + + public function setMap(Map $map): void + { + $this->map = $map; + } + + public function setMapId(int $mapId): void + { + $this->mapId = $mapId; + } + + public function setPosition(Position $position): void + { + $this->position = $position; + } + + public function setLat(float $lat): void + { + $this->position->setLat($lat); + } + + public function setLng(float $lng): void + { + $this->position->setLng($lng); + } + + public function setPanoIdCached(?string $panoIdCached): void + { + $this->panoIdCached = $panoIdCached; + } + + public function setPanoIdCachedTimestampDate(?DateTime $panoIdCachedTimestamp): void + { + $this->panoIdCachedTimestamp = $panoIdCachedTimestamp; + } + + public function setPanoIdCachedTimestamp(?string $panoIdCachedTimestamp): void + { + if ($panoIdCachedTimestamp !== null) { + $this->panoIdCachedTimestamp = new DateTime($panoIdCachedTimestamp); + } + } + + public function getMap(): ?Map + { + return $this->map; + } + + public function getMapId(): ?int + { + return $this->mapId; + } + + public function getPosition(): Position + { + return $this->position; + } + + public function getLat(): float + { + return $this->position->getLat(); + } + + public function getLng(): float + { + return $this->position->getLng(); + } + + public function getFreshPanoId(bool $canBeIndoor = false): ?string + { + if ( + $this->panoIdCachedTimestamp !== null && + (clone $this->panoIdCachedTimestamp)->add(new DateInterval('P1D')) > new DateTime() + ) { + return $this->panoIdCached; + } + + $request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET); + $request->setQuery([ + 'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'], + 'location' => $this->position->getLat() . ',' . $this->position->getLng(), + 'source' => $canBeIndoor ? 'default' : 'outdoor' + ]); + + $response = $request->send(); + $panoData = json_decode($response->getBody(), true); + $panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null; + + // enable indoor panos if no outdoor found + if ($panoId === null && !$canBeIndoor) { + return $this->getFreshPanoId(true); + } + + $this->panoIdCached = $panoId; + $this->panoIdCachedTimestamp = new DateTime('now'); + + (new PersistentDataManager())->saveToDb($this); + + return $panoId; + } + + public function getPanoIdCached(): ?string + { + return $this->panoIdCached; + } + + public function getPanoIdCachedTimestampDate(): ?DateTime + { + return $this->panoIdCachedTimestamp; + } + + public function getPanoIdCachedTimestamp(): ?string + { + if ($this->panoIdCachedTimestamp === null) { + return null; + } + + return $this->panoIdCachedTimestamp->format('Y-m-d H:i:s'); + } +} diff --git a/src/Model/User.php b/src/PersistentData/Model/User.php similarity index 86% rename from src/Model/User.php rename to src/PersistentData/Model/User.php index 0814a78..fc3b185 100644 --- a/src/Model/User.php +++ b/src/PersistentData/Model/User.php @@ -1,16 +1,18 @@ -id; + } + public function getDisplayName(): string { return $this->email; diff --git a/src/PersistentData/Model/UserConfirmation.php b/src/PersistentData/Model/UserConfirmation.php new file mode 100644 index 0000000..70a76d6 --- /dev/null +++ b/src/PersistentData/Model/UserConfirmation.php @@ -0,0 +1,44 @@ +user = $user; + } + + public function setUserId(int $userId): void + { + $this->userId = $userId; + } + + public function setToken(string $token): void + { + $this->token = $token; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function getToken(): string + { + return $this->token; + } +} diff --git a/src/PersistentData/PersistentDataManager.php b/src/PersistentData/PersistentDataManager.php new file mode 100644 index 0000000..3965066 --- /dev/null +++ b/src/PersistentData/PersistentDataManager.php @@ -0,0 +1,206 @@ +from($table); + + //TODO: only with some relations? + if ($withRelations) { + $relations = call_user_func([$type, 'getRelations']); + + $columns = []; + + foreach ($fields as $field) { + $columns[] = [$table, $field]; + } + + $columns = array_merge($columns, $this->getRelationColumns($relations)); + + $this->leftJoinRelations($select, $table, $relations); + $select->columns($columns); + } else { + $select->columns($fields); + } + + //TODO: return with array? + $data = $select->execute()->fetch(IResultSet::FETCH_ASSOC); + + if ($data === null) { + return null; + } + + $model = new $type(); + $this->fillWithData($data, $model); + + return $model; + } + + public function selectFromDbById($id, string $type, bool $withRelations = false): ?Model + { + $select = new Select(\Container::$dbConnection); + $select->whereId($id); + + return $this->selectFromDb($select, $type, $withRelations); + } + + public function fillWithData(array $data, Model $model): void + { + $relations = $model::getRelations(); + $relationData = []; + + foreach ($data as $key => $value) { + if ($this->extractRelationData($key, $value, $relationData, $relations)) { + continue; + } + + $method = 'set' . str_replace('_', '', ucwords($key, '_')); + + if (method_exists($model, $method)) { + $model->$method($value); + } + } + + $this->setRelations($model, $relationData); + + $model->saveSnapshot(); + } + + public function loadRelationsFromDb(Model $model, bool $recursive): void + { + foreach ($model::getRelations() as $relation => $relationType) { + $camel = str_replace('_', '', ucwords($relation, '_')); + + $methodGet = 'get' . $camel . 'Id'; + $methodSet = 'set' . $camel; + + $relationId = $model->$methodGet(); + + if ($relationId !== null) { + $relationModel = $this->selectFromDbById($relationId, $relationType, $recursive); + + $model->$methodSet($relationModel); + } + } + } + + public function saveToDb(Model $model): void + { + $this->syncRelations($model); + + $modified = $model->toArray(); + $id = $model->getId(); + + $modify = new Modify(\Container::$dbConnection, $model::getTable()); + + if ($id !== null) { + $original = $model->getSnapshot(); + + foreach ($original as $key => $value) { + if ($value === $modified[$key]) { + unset($modified[$key]); + } + } + + if (count($modified) > 0) { + $modify->setId($id); + $modify->fill($modified); + $modify->save(); + } + } else { + $modify->fill($modified); + $modify->save(); + + $model->setId($modify->getId()); + } + + $model->saveSnapshot(); + } + + public function deleteFromDb(Model $model): void + { + $modify = new Modify(\Container::$dbConnection, $model::getTable()); + $modify->setId($model->getId()); + $modify->delete(); + + $model->setId(null); + $model->resetSnapshot(); + } + + private function getRelationColumns(array $relations): array + { + $columns = []; + + foreach ($relations as $relation => $relationType) { + $relationTable = call_user_func([$relationType, 'getTable']); + foreach (call_user_func([$relationType, 'getFields']) as $relationField) { + $columns[] = [$relationTable, $relationField, $relation . '__' . $relationField]; + } + } + + return $columns; + } + + private function leftJoinRelations(Select $select, string $table, array $relations): void + { + foreach ($relations as $relation => $relationType) { + $relationTable = call_user_func([$relationType, 'getTable']); + $select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']); + } + } + + private function extractRelationData(string $key, $value, array &$relationData, array $relations): bool + { + $found = false; + + foreach ($relations as $relation => $relationType) { + if (substr($key, 0, strlen($relation . '__')) === $relation . '__') { + $found = true; + $relationData[$relation][substr($key, strlen($relation . '__'))] = $value; + break; + } + } + + return $found; + } + + private function setRelations(Model $model, array &$relations): void + { + foreach ($model::getRelations() as $relation => $relationType) { + if (isset($relations[$relation])) { + $object = new $relationType(); + + $this->fillWithData($relations[$relation], $object); + + $method = 'set' . str_replace('_', '', ucwords($relation, '_')); + + $model->$method($object); + } + } + } + + private function syncRelations(Model $model): void + { + foreach ($model::getRelations() as $relation => $relationType) { + $camel = str_replace('_', '', ucwords($relation, '_')); + + $methodGet = 'get' . $camel; + $methodSet = 'set' . $camel . 'Id'; + + $relationModel = $model->$methodGet(); + + if ($relationModel !== null) { + $model->$methodSet($relationModel->getId()); + } + } + } +} diff --git a/src/Repository/MapRepository.php b/src/Repository/MapRepository.php index ba9e11a..cfb9885 100644 --- a/src/Repository/MapRepository.php +++ b/src/Repository/MapRepository.php @@ -1,16 +1,19 @@ columns(['id', 'name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area']); - $select->whereId($mapId); + private PersistentDataManager $pdm; - return $select->execute()->fetch(IResultSet::FETCH_ASSOC); + public function __construct() + { + $this->pdm = new PersistentDataManager(); + } + + public function getById(int $mapId): ?Map + { + return $this->pdm->selectFromDbById($mapId, Map::class); } } diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index 2a6b325..16f2afe 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -1,95 +1,43 @@ selectFromDbById($placeId); - - $panoId = $this->requestPanoId($place); - - $position = new Position($place['lat'], $place['lng']); - - return [ - 'position' => $position, - 'panoId' => $panoId - ]; + $this->pdm = new PersistentDataManager(); } - public function getForMapWithValidPano(int $mapId, array $exclude = []): array + public function getById(int $placeId): ?Place + { + return $this->pdm->selectFromDbById($placeId, Place::class); + } + + public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place { $placesWithoutPano = []; do { - $place = $this->selectFromDbForMap($mapId, $exclude); + $place = $this->selectRandomFromDbForMap($mapId, $exclude); - $panoId = $this->requestPanoId($place); + $panoId = $place->getFreshPanoId(); if ($panoId === null) { - $placesWithoutPano[] = $exclude[] = $place['id']; + $placesWithoutPano[] = $exclude[] = $place->getId(); } } while ($panoId === null); - $position = new Position($place['lat'], $place['lng']); - - return [ - 'placesWithoutPano' => $placesWithoutPano, - 'placeId' => $place['id'], - 'position' => $position, - 'panoId' => $panoId - ]; - } - - public function addToMap(int $mapId, array $place): int - { - $modify = new Modify(\Container::$dbConnection, 'places'); - $modify->set('map_id', $mapId); - $modify->fill($place); - $modify->save(); - - return $modify->getId(); - } - - public function modify(int $id, array $place): void - { - $modify = new Modify(\Container::$dbConnection, 'places'); - $modify->setId($id); - $modify->set('pano_id_cached', null); - $modify->set('pano_id_cached_timestamp', null); - $modify->fill($place); - $modify->save(); - } - - public function delete(int $id): void - { - $modify = new Modify(\Container::$dbConnection, 'places'); - $modify->setId($id); - $modify->delete(); - } - - private function selectFromDbById(int $placeId): array - { - $select = new Select(\Container::$dbConnection, 'places'); - $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']); - $select->whereId($placeId); - - $place = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - return $place; } - private function selectFromDbForMap(int $mapId, array $exclude): array + private function selectRandomFromDbForMap(int $mapId, array $exclude): ?Place { $select = new Select(\Container::$dbConnection, 'places'); - $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']); $select->where('id', 'NOT IN', $exclude); $select->where('map_id', '=', $mapId); @@ -99,49 +47,6 @@ class PlaceRepository $select->orderBy('id'); $select->limit(1, $randomOffset); - $place = $select->execute()->fetch(IResultSet::FETCH_ASSOC); - - return $place; - } - - private function requestPanoId(array $place, bool $canBeIndoor = false): ?string - { - if ( - $place['pano_id_cached_timestamp'] && - (new DateTime($place['pano_id_cached_timestamp']))->add(new DateInterval('P1D')) > new DateTime() - ) { - return $place['pano_id_cached']; - } - - $request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET); - $request->setQuery([ - 'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'], - 'location' => $place['lat'] . ',' . $place['lng'], - 'source' => $canBeIndoor ? 'default' : 'outdoor' - ]); - - $response = $request->send(); - - $panoData = json_decode($response->getBody(), true); - - $panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null; - - // enable indoor panos if no outdoor found - if ($panoId === null && !$canBeIndoor) { - return $this->requestPanoId($place, true); - } - - $this->saveCachedPanoId($place['id'], $panoId); - - return $panoId; - } - - private function saveCachedPanoId(int $placeId, ?string $panoId): void - { - $modify = new Modify(\Container::$dbConnection, 'places'); - $modify->setId($placeId); - $modify->set('pano_id_cached', $panoId); - $modify->set('pano_id_cached_timestamp', (new DateTime())->format('Y-m-d H:i:s')); - $modify->save(); + return $this->pdm->selectFromDb($select, Place::class); } } diff --git a/src/Repository/UserConfirmationRepository.php b/src/Repository/UserConfirmationRepository.php new file mode 100644 index 0000000..62df3ae --- /dev/null +++ b/src/Repository/UserConfirmationRepository.php @@ -0,0 +1,28 @@ +pdm = new PersistentDataManager(); + } + + public function getById(int $userConfirmationId): ?UserConfirmation + { + return $this->pdm->selectFromDbById($userConfirmationId, UserConfirmation::class); + } + + public function getByToken(string $token): ?UserConfirmation + { + $select = new Select(\Container::$dbConnection); + $select->where('token', '=', $token); + + return $this->pdm->selectFromDb($select, UserConfirmation::class); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..b67771d --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,28 @@ +pdm = new PersistentDataManager(); + } + + public function getById(int $userId): ?User + { + return $this->pdm->selectFromDbById($userId, User::class); + } + + public function getByEmail(string $email): ?User + { + $select = new Select(\Container::$dbConnection); + $select->where('email', '=', $email); + + return $this->pdm->selectFromDb($select, User::class); + } +} diff --git a/src/Request/Request.php b/src/Request/Request.php index fb95646..0b41fa7 100644 --- a/src/Request/Request.php +++ b/src/Request/Request.php @@ -3,6 +3,9 @@ use MapGuesser\Interfaces\Authentication\IUser; use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\ISession; +use MapGuesser\PersistentData\Model\User; +use MapGuesser\PersistentData\PersistentDataManager; +use MapGuesser\Repository\UserRepository; class Request implements IRequest { @@ -16,12 +19,24 @@ class Request implements IRequest private Session $session; + private UserRepository $userRepository; + + private ?User $user = null; + public function __construct(string $base, array &$get, array &$post, array &$session) { $this->base = $base; $this->get = &$get; $this->post = &$post; $this->session = new Session($session); + + $this->userRepository = new UserRepository(); + + $userId = $this->session->get('userId'); + + if ($userId !== null) { + $this->user = $this->userRepository->getById($userId); + } } public function setParsedRouteParams(array &$routeParams) @@ -61,8 +76,18 @@ class Request implements IRequest return $this->session; } + public function setUser(?IUser $user): void + { + if ($user === null) { + $this->session->delete('userId'); + return; + } + + $this->session->set('userId', $user->getUniqueId()); + } + public function user(): ?IUser { - return $this->session->get('user'); + return $this->user; } } diff --git a/src/Util/Geo/Bounds.php b/src/Util/Geo/Bounds.php index 72501c1..99f1edb 100644 --- a/src/Util/Geo/Bounds.php +++ b/src/Util/Geo/Bounds.php @@ -59,6 +59,26 @@ class Bounds } } + public function setSouthLat(float $southLat): void + { + $this->southLat = $southLat; + } + + public function setWestLng(float $westLng): void + { + $this->westLng = $westLng; + } + + public function setNorthLat(float $northLat): void + { + $this->northLat = $northLat; + } + + public function setEastLng(float $eastLng): void + { + $this->eastLng = $eastLng; + } + public function getSouthLat(): float { return $this->southLat; diff --git a/src/Util/Geo/Position.php b/src/Util/Geo/Position.php index 33eb0cd..bdd89ca 100644 --- a/src/Util/Geo/Position.php +++ b/src/Util/Geo/Position.php @@ -13,6 +13,16 @@ class Position $this->lng = $lng; } + public function setLat(float $lat): void + { + $this->lat = $lat; + } + + public function setLng(float $lng): void + { + $this->lng = $lng; + } + public function getLat(): float { return $this->lat;